From 4e9fb6d4180a84aa862116a1c13142b5f65b6205 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Sat, 30 Mar 2024 21:49:17 +0100 Subject: [PATCH 01/24] Make sure pathFilter is applied to workspace module content tree (cherry picked from commit d746e823bfcb7d17c5e6ad03c3e8433af68a8ac8) --- .../maven/dependency/ResolvedDependency.java | 3 +- .../io/quarkus/paths/FilteredPathTree.java | 99 ++++++++++++ .../quarkus/paths/FilteredPathTreeTest.java | 152 ++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/FilteredPathTreeTest.java diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java index 0c068618940d8..e22e23a961671 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java @@ -5,6 +5,7 @@ import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.paths.EmptyPathTree; +import io.quarkus.paths.FilteredPathTree; import io.quarkus.paths.MultiRootPathTree; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathFilter; @@ -36,7 +37,7 @@ default PathTree getContentTree(PathFilter pathFilter) { final WorkspaceModule module = getWorkspaceModule(); final PathTree workspaceTree = module == null ? EmptyPathTree.getInstance() : module.getContentTree(getClassifier()); if (!workspaceTree.isEmpty()) { - return workspaceTree; + return pathFilter == null ? workspaceTree : new FilteredPathTree(workspaceTree, pathFilter); } final PathCollection paths = getResolvedPaths(); if (paths == null || paths.isEmpty()) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java new file mode 100644 index 0000000000000..11e125a04fbf1 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilteredPathTree.java @@ -0,0 +1,99 @@ +package io.quarkus.paths; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.jar.Manifest; + +public class FilteredPathTree implements PathTree { + + private final PathTree original; + protected final PathFilter filter; + + public FilteredPathTree(PathTree tree, PathFilter filter) { + this.original = Objects.requireNonNull(tree, "tree is null"); + this.filter = Objects.requireNonNull(filter, "filter is null"); + } + + @Override + public Collection getRoots() { + return original.getRoots(); + } + + @Override + public Manifest getManifest() { + return original.getManifest(); + } + + @Override + public void walk(PathVisitor visitor) { + original.walk(visit -> { + if (visit != null && filter.isVisible(visit.getRelativePath("/"))) { + visitor.visitPath(visit); + } + }); + } + + @Override + public T apply(String relativePath, Function func) { + if (!PathFilter.isVisible(filter, relativePath)) { + return func.apply(null); + } + return original.apply(relativePath, func); + } + + @Override + public void accept(String relativePath, Consumer consumer) { + if (!PathFilter.isVisible(filter, relativePath)) { + consumer.accept(null); + } else { + original.accept(relativePath, consumer); + } + } + + @Override + public boolean contains(String relativePath) { + return PathFilter.isVisible(filter, relativePath) && original.contains(relativePath); + } + + @Override + public OpenPathTree open() { + return new OpenFilteredPathTree(original.open(), filter); + } + + private static class OpenFilteredPathTree extends FilteredPathTree implements OpenPathTree { + + private final OpenPathTree original; + + private OpenFilteredPathTree(OpenPathTree original, PathFilter filter) { + super(original, filter); + this.original = original; + } + + @Override + public PathTree getOriginalTree() { + return original.getOriginalTree(); + } + + @Override + public boolean isOpen() { + return original.isOpen(); + } + + @Override + public Path getPath(String relativePath) { + if (!PathFilter.isVisible(filter, relativePath)) { + return null; + } + return original.getPath(relativePath); + } + + @Override + public void close() throws IOException { + original.close(); + } + } +} diff --git a/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/FilteredPathTreeTest.java b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/FilteredPathTreeTest.java new file mode 100644 index 0000000000000..03a76ffea14f8 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/paths/FilteredPathTreeTest.java @@ -0,0 +1,152 @@ +package io.quarkus.paths; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import io.quarkus.fs.util.ZipUtils; + +public class FilteredPathTreeTest { + + @TempDir + static Path testDir; + static Path testJar; + + private static void createFile(String path) throws Exception { + var file = testDir.resolve(path); + Files.createDirectories(file.getParent()); + Files.createFile(file); + } + + @BeforeAll + public static void beforeAll() throws Exception { + createFile("META-INF/jandex.idx"); + createFile("org/toolbox/Axe.class"); + createFile("org/toolbox/Hammer.class"); + createFile("org/toolbox/Saw.class"); + createFile("README.md"); + testJar = testDir.resolve("test.jar"); + ZipUtils.zip(testDir, testJar); + } + + @Test + public void unfilteredTestDir() { + var pathTree = PathTree.ofDirectoryOrArchive(testDir); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "", + "META-INF", + "META-INF/jandex.idx", + "org", + "org/toolbox", + "org/toolbox/Axe.class", + "org/toolbox/Hammer.class", + "org/toolbox/Saw.class", + "README.md", + "test.jar"); + } + + @Test + public void unfilteredTestJar() { + var pathTree = PathTree.ofDirectoryOrArchive(testJar); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "", + "META-INF", + "META-INF/jandex.idx", + "org", + "org/toolbox", + "org/toolbox/Axe.class", + "org/toolbox/Hammer.class", + "org/toolbox/Saw.class", + "README.md", + "test.jar"); + } + + @Test + public void dirIncludeToolbox() { + var pathTree = PathTree.ofDirectoryOrArchive(testDir, PathFilter.forIncludes(List.of("*/toolbox/**"))); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "org/toolbox/Axe.class", + "org/toolbox/Hammer.class", + "org/toolbox/Saw.class"); + } + + @Test + public void jarIncludeToolbox() { + var pathTree = PathTree.ofDirectoryOrArchive(testJar, PathFilter.forIncludes(List.of("*/toolbox/**"))); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "org/toolbox/Axe.class", + "org/toolbox/Hammer.class", + "org/toolbox/Saw.class"); + } + + @Test + public void dirIncludeToolboxExcludeHammer() { + var pathTree = PathTree.ofDirectoryOrArchive(testDir, new PathFilter( + List.of("*/toolbox/**"), + List.of("**/Hammer.class"))); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "org/toolbox/Axe.class", + "org/toolbox/Saw.class"); + } + + @Test + public void jarIncludeToolboxExcludeHammer() { + var pathTree = PathTree.ofDirectoryOrArchive(testJar, new PathFilter( + List.of("*/toolbox/**"), + List.of("**/Hammer.class"))); + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "org/toolbox/Axe.class", + "org/toolbox/Saw.class"); + } + + @Test + public void filteredPathTree() throws Exception { + var originalFilter = new PathFilter( + List.of("*/toolbox/**"), + List.of("**/Hammer.class")); + var outerFilter = new PathFilter( + List.of("**/Axe.class"), + List.of("**/Saw.class")); + + var pathTree = PathTree.ofDirectoryOrArchive(testDir, originalFilter); + pathTree = new FilteredPathTree(pathTree, outerFilter); + assertFilteredPathTree(pathTree); + try (var openTree = pathTree.open()) { + assertFilteredPathTree(openTree); + } + + pathTree = PathTree.ofDirectoryOrArchive(testJar, originalFilter); + pathTree = new FilteredPathTree(pathTree, outerFilter); + assertFilteredPathTree(pathTree); + try (var openTree = pathTree.open()) { + assertFilteredPathTree(openTree); + } + } + + private static void assertFilteredPathTree(PathTree pathTree) { + assertThat(getAllPaths(pathTree)).containsExactlyInAnyOrder( + "org/toolbox/Axe.class"); + + assertThat(pathTree.isEmpty()).isFalse(); + assertThat(pathTree.contains("org/toolbox/Axe.class")).isTrue(); + assertThat(pathTree.apply("org/toolbox/Axe.class", Objects::nonNull)).isTrue(); + assertThat(pathTree.apply("org/toolbox/Saw.class", Objects::nonNull)).isFalse(); + pathTree.accept("org/toolbox/Axe.class", visit -> assertThat(visit).isNotNull()); + pathTree.accept("org/toolbox/Saw.class", visit -> assertThat(visit).isNull()); + } + + private static Set getAllPaths(PathTree pathTree) { + final Set paths = new HashSet<>(); + pathTree.walk(visit -> paths.add(visit.getRelativePath("/"))); + return paths; + } +} From 16e304a00e1c302a705997dfb44b9b8d58d9c09e Mon Sep 17 00:00:00 2001 From: Yukihiro Okada Date: Tue, 2 Apr 2024 14:32:25 +0900 Subject: [PATCH 02/24] Bump JDK version to 17 in aws-lambda related extensions (cherry picked from commit d9a771f3f73c0a276e30c866963240f951e8c719) --- docs/src/main/asciidoc/aws-lambda-http.adoc | 2 +- .../deployment/src/main/resources/http/sam.jvm.yaml | 2 +- .../deployment/src/main/resources/http/sam.jvm.yaml | 2 +- .../common-deployment/src/main/resources/lambda/manage.sh | 2 +- .../common-deployment/src/main/resources/lambda/sam.jvm.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/aws-lambda-http.adoc b/docs/src/main/asciidoc/aws-lambda-http.adoc index 517a31bf99545..158eaefd97739 100644 --- a/docs/src/main/asciidoc/aws-lambda-http.adoc +++ b/docs/src/main/asciidoc/aws-lambda-http.adoc @@ -265,7 +265,7 @@ Do not change the Lambda handler name. ---- Properties: Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest - Runtime: java11 + Runtime: java17 ---- This handler is a bridge between the lambda runtime and the Quarkus HTTP framework you are using (Jakarta REST, Servlet, etc.) diff --git a/extensions/amazon-lambda-http/deployment/src/main/resources/http/sam.jvm.yaml b/extensions/amazon-lambda-http/deployment/src/main/resources/http/sam.jvm.yaml index 0d41bbbf07590..60b4175a98b92 100644 --- a/extensions/amazon-lambda-http/deployment/src/main/resources/http/sam.jvm.yaml +++ b/extensions/amazon-lambda-http/deployment/src/main/resources/http/sam.jvm.yaml @@ -12,7 +12,7 @@ Type: AWS::Serverless::Function Properties: Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest - Runtime: java11 + Runtime: java17 CodeUri: function.zip MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/extensions/amazon-lambda-rest/deployment/src/main/resources/http/sam.jvm.yaml b/extensions/amazon-lambda-rest/deployment/src/main/resources/http/sam.jvm.yaml index a2476a2a1a648..b965648cd1c7b 100644 --- a/extensions/amazon-lambda-rest/deployment/src/main/resources/http/sam.jvm.yaml +++ b/extensions/amazon-lambda-rest/deployment/src/main/resources/http/sam.jvm.yaml @@ -12,7 +12,7 @@ Type: AWS::Serverless::Function Properties: Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest - Runtime: java11 + Runtime: java17 CodeUri: function.zip MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/manage.sh b/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/manage.sh index 6990202eca9ea..acfca769bf970 100644 --- a/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/manage.sh +++ b/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/manage.sh @@ -51,7 +51,7 @@ function cmd_update() { FUNCTION_NAME=${lambdaName} HANDLER=${handler} -RUNTIME=java11 +RUNTIME=java17 ZIP_FILE=${targetUri} function usage() { diff --git a/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/sam.jvm.yaml b/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/sam.jvm.yaml index 7efbe0de60fc7..b4d67fca6a950 100644 --- a/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/sam.jvm.yaml +++ b/extensions/amazon-lambda/common-deployment/src/main/resources/lambda/sam.jvm.yaml @@ -12,7 +12,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${handler} - Runtime: java11 + Runtime: java17 CodeUri: function.zip MemorySize: 256 Timeout: 15 From 915e16f472d31ca63bbf4f21c62ea4d956d54e9a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 Apr 2024 10:22:46 +0300 Subject: [PATCH 03/24] Add maxLength configuration option to SysLog Closes: #39944 (cherry picked from commit 0e33c10eb2bb03bd74e418ae511ce6f651e173bb) --- .../runtime/logging/LoggingSetupRecorder.java | 19 +++++++++++++++++++ .../quarkus/runtime/logging/SyslogConfig.java | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index 6b37b15685d7d..6108893f04013 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -6,6 +6,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -687,6 +688,24 @@ private static Handler configureSyslogHandler(final SyslogConfig config, final E handler.setTruncate(config.truncate); handler.setUseCountingFraming(config.useCountingFraming); handler.setLevel(config.level); + if (config.maxLength.isPresent()) { + BigInteger maxLen = config.maxLength.get().asBigInteger(); + if (maxLen.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { + errorManager.error( + "Using 2GB as the value of maxLength for SyslogHandler as it is the maximum allowed value", null, + ErrorManager.GENERIC_FAILURE); + maxLen = BigInteger.valueOf(Integer.MAX_VALUE); + } else { + BigInteger minimumAllowedMaxLength = BigInteger.valueOf(128); + if (maxLen.compareTo(minimumAllowedMaxLength) < 0) { + errorManager.error( + "Using 128 as the value of maxLength for SyslogHandler as using a smaller value is not allowed", + null, ErrorManager.GENERIC_FAILURE); + maxLen = minimumAllowedMaxLength; + } + } + handler.setMaxLength(maxLen.intValue()); + } Formatter formatter = null; boolean formatterWarning = false; diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/SyslogConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/SyslogConfig.java index 0411d345fdcc3..767a0a5b92c85 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/SyslogConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/SyslogConfig.java @@ -10,6 +10,7 @@ import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.configuration.MemorySize; @ConfigGroup public class SyslogConfig { @@ -95,6 +96,15 @@ public class SyslogConfig { @ConfigItem Optional filter; + /** + * The maximum length, in bytes, of the message allowed to be sent. The length includes the header and the message. + *

+ * If not set, the default value is {@code 2048} when {@code sys-log-type} is {@code rfc5424} (which is the default) + * and {@code 1024} when {@code sys-log-type} is {@code rfc3164} + */ + @ConfigItem + Optional maxLength; + /** * Syslog async logging config */ From aa9b98c78c0e63d041a497723a31566ce40b8748 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 10 Apr 2024 11:42:12 +0200 Subject: [PATCH 04/24] Parameter to skip Maven goal executions before quarkus:dev, skipping flatten plugin by default (cherry picked from commit ad43bac691cf6e0bbf16e45c258243f80e5daf69) --- .../main/java/io/quarkus/maven/DevMojo.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index bdcd48eeff5d3..9dc4a552aad8c 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -155,11 +155,6 @@ public class DevMojo extends AbstractMojo { private static final String ORG_JETBRAINS_KOTLIN = "org.jetbrains.kotlin"; private static final String KOTLIN_MAVEN_PLUGIN = "kotlin-maven-plugin"; - private static final String IO_SMALLRYE = "io.smallrye"; - private static final String ORG_JBOSS_JANDEX = "org.jboss.jandex"; - private static final String JANDEX_MAVEN_PLUGIN = "jandex-maven-plugin"; - private static final String JANDEX = "jandex"; - private static final String BOOTSTRAP_ID = "DevMojo"; /** @@ -367,6 +362,17 @@ public class DevMojo extends AbstractMojo { @Component BuildAnalyticsProvider analyticsProvider; + /** + * A comma-separated list of Maven plugin keys in {@code groupId:artifactId} format + * (for example {@code org.codehaus.mojo:flatten-maven-plugin} and/or goal prefixes, + * (for example {@code flatten}) that should be skipped when {@code quarkus:dev} identifies + * Maven plugin goals that should be executed before the application is launched in dev mode. + *

+ * Only the {@code flatten} Maven plugin is skipped by default. + */ + @Parameter(defaultValue = "org.codehaus.mojo:flatten-maven-plugin") + Set skipPlugins; + /** * console attributes, used to restore the console state */ @@ -587,6 +593,12 @@ private String handleAutoCompile() throws MojoExecutionException { if (p.getExecutions().isEmpty()) { continue; } + if (skipPlugins.contains(p.getKey())) { + if (getLog().isDebugEnabled()) { + getLog().debug("Skipping " + p.getId() + " execution according to skipPlugins value"); + } + continue; + } for (PluginExecution e : p.getExecutions()) { if (e.getPhase() != null && !PRE_DEV_MODE_PHASES.contains(e.getPhase())) { // skip executions with phases post quarkus:dev, such as install, deploy, site, etc @@ -598,6 +610,13 @@ private String handleAutoCompile() throws MojoExecutionException { String goalPrefix = null; if (!e.getGoals().isEmpty()) { goalPrefix = getMojoDescriptor(p, e.getGoals().get(0)).getPluginDescriptor().getGoalPrefix(); + if (skipPlugins.contains(goalPrefix)) { + if (getLog().isDebugEnabled()) { + getLog().debug("Skipping " + goalPrefix + " execution according to skipPlugins value"); + continue; + } + continue; + } pluginPrefixes.put(goalPrefix, p); pluginPrefixes.put(p.getId(), p); } From 1f6f6c89c70ec45889a926fdabddfd0766e113b1 Mon Sep 17 00:00:00 2001 From: Jean Bisutti Date: Wed, 10 Apr 2024 14:50:27 +0200 Subject: [PATCH 05/24] Update OpenTelemetry exporter link (cherry picked from commit 0a73e575302814535e03a434ffc52d8347fbc4ab) --- docs/src/main/asciidoc/opentelemetry.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index 9e565536d89f8..0c4aa4dc102e3 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -628,7 +628,7 @@ The exporter is automatically wired with CDI, that's why the `quarkus.otel.trace The `quarkus.otel.exporter.otlp.traces.protocol` default to `grpc` and `http/protobuf` can also be used. === On Quarkiverse -Additional exporters will be available in the Quarkiverse https://github.com/quarkiverse/quarkus-opentelemetry-exporter/blob/main/README.md[quarkus-opentelemetry-exporter] project. +Additional exporters will be available in the Quarkiverse https://docs.quarkiverse.io/quarkus-opentelemetry-exporter/dev/index.html[quarkus-opentelemetry-exporter] project. [[quarkus-extensions-using-opentelemetry]] == Quarkus core extensions instrumented with OpenTelemetry tracing From 7095eac3167c4b50ee167c4fabda02f72bdefbd7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 10 Apr 2024 18:46:44 +0200 Subject: [PATCH 06/24] Go back to raw Maven read/write for bootstrap Follow up of https://github.com/quarkusio/quarkus/pull/39382. Per gripe from Alexey. (cherry picked from commit 68a1bbe7424ff482bb2c63f76219eb2591c2a394) --- independent-projects/bootstrap/bom/pom.xml | 5 ---- .../bootstrap/maven-resolver/pom.xml | 4 --- .../resolver/maven/workspace/ModelUtils.java | 30 +++++++------------ independent-projects/bootstrap/pom.xml | 1 - 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/independent-projects/bootstrap/bom/pom.xml b/independent-projects/bootstrap/bom/pom.xml index 10504e6d084e3..5db6112b194ad 100644 --- a/independent-projects/bootstrap/bom/pom.xml +++ b/independent-projects/bootstrap/bom/pom.xml @@ -481,11 +481,6 @@ - - io.fabric8 - maven-model-helper - ${maven-model-helper.version} - io.smallrye.common diff --git a/independent-projects/bootstrap/maven-resolver/pom.xml b/independent-projects/bootstrap/maven-resolver/pom.xml index b6c21737f4c89..97ce31340034c 100644 --- a/independent-projects/bootstrap/maven-resolver/pom.xml +++ b/independent-projects/bootstrap/maven-resolver/pom.xml @@ -125,10 +125,6 @@ - - io.fabric8 - maven-model-helper - org.junit.jupiter junit-jupiter diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java index 9786fbb004fc0..97f6c8f1456d4 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java @@ -1,9 +1,9 @@ package io.quarkus.bootstrap.resolver.maven.workspace; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -16,9 +16,10 @@ import org.apache.maven.model.Model; import org.apache.maven.model.Parent; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import io.fabric8.maven.Maven; -import io.fabric8.maven.XMLFormat; import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ArtifactCoords; @@ -231,30 +232,21 @@ private static Properties loadPomProps(Path appJar, Path artifactIdPath) throws } public static Model readModel(final Path pomXml) throws IOException { - try { - return Maven.readModel(pomXml); - } catch (UncheckedIOException e) { - throw e.getCause(); - } catch (RuntimeException e) { - throw new IOException("Failed to read model", e.getCause()); - } + return readModel(Files.newInputStream(pomXml)); } public static Model readModel(InputStream stream) throws IOException { try (InputStream is = stream) { - return Maven.readModel(is); - } catch (UncheckedIOException e) { - throw e.getCause(); - } catch (RuntimeException e) { - throw new IOException("Failed to read model", e.getCause()); + return new MavenXpp3Reader().read(stream); + } catch (XmlPullParserException e) { + throw new IOException("Failed to parse POM", e); } } public static void persistModel(Path pomFile, Model model) throws IOException { - try { - Maven.writeModel(model, pomFile, XMLFormat.builder().indent(" ").insertLineBreakBetweenMajorSections().build()); - } catch (UncheckedIOException e) { - throw e.getCause(); + final MavenXpp3Writer xpp3Writer = new MavenXpp3Writer(); + try (BufferedWriter pomFileWriter = Files.newBufferedWriter(pomFile)) { + xpp3Writer.write(pomFileWriter, model); } } } diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index efe3e1ca44126..b163ec4e08a44 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -80,7 +80,6 @@ 0.1.3 2.23.0 1.9.0 - 36 bom From ab57188ddd030ea89149ae7b630300a6a8a58d18 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 11 Apr 2024 15:12:05 +1000 Subject: [PATCH 07/24] Fix url encoding issue for Dev UI Page with funny chars Signed-off-by: Phillip Kruger (cherry picked from commit 5e95a7b34a4c0ab0ea4d9256c5134bf0a3d78b22) --- .../resources/dev-ui/controller/router-controller.js | 1 - .../src/main/java/io/quarkus/devui/spi/page/Page.java | 9 +++++++++ .../main/java/io/quarkus/devui/spi/page/PageBuilder.java | 5 ++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js index a70a2a0efd2e9..3fd5f879c6e6d 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js @@ -176,7 +176,6 @@ export class RouterController { addRoute(path, component, name, page, defaultRoute = false) { path = this.getPageUrlFor(page); - var currentSelection = window.location.pathname; const search = new URLSearchParams(window.location.search); if (!this.isExistingPath(path)) { RouterController.pageMap.set(path, page); diff --git a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/Page.java b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/Page.java index 247b2986f1c14..e7cb77009afbf 100644 --- a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/Page.java +++ b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/Page.java @@ -1,5 +1,8 @@ package io.quarkus.devui.spi.page; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Map; /** @@ -61,6 +64,12 @@ protected Page(String icon, public String getId() { String id = this.title.toLowerCase().replaceAll(SPACE, DASH); + try { + id = URLEncoder.encode(id, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException(ex); + } + if (!this.isInternal() && this.namespace != null) { // This is extension pages in Dev UI id = this.namespace.toLowerCase() + SLASH + id; diff --git a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/PageBuilder.java b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/PageBuilder.java index 0e77a50ca6b73..823fd87570a83 100644 --- a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/PageBuilder.java +++ b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/PageBuilder.java @@ -42,7 +42,10 @@ public T title(String title) { @SuppressWarnings("unchecked") public T staticLabel(String staticLabel) { - this.staticLabel = staticLabel; + if (this.staticLabel == null) { + this.staticLabel = ""; + } + this.staticLabel = this.staticLabel + " " + staticLabel; return (T) this; } From 9ff89aba56878f1b80223bc803b4dc7329dcb34d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 11 Apr 2024 18:42:50 +0300 Subject: [PATCH 08/24] Fix broken pre-match test (cherry picked from commit 47d782f31e222abe19061cf290a9cf2120b502b3) --- ...nHeader.java => PreMatchAcceptInHeaderTest.java} | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) rename independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/{PreMatchAcceptInHeader.java => PreMatchAcceptInHeaderTest.java} (90%) diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java similarity index 90% rename from independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java rename to independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java index cfbd44a4795fc..eee195df3ce55 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java @@ -23,15 +23,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public class PreMatchAcceptInHeader { +public class PreMatchAcceptInHeaderTest { @RegisterExtension static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() .setArchiveProducer(new Supplier<>() { @Override public JavaArchive get() { - return ShrinkWrap.create(JavaArchive.class) - .addClass(PathSegmentTest.Resource.class); + return ShrinkWrap.create(JavaArchive.class); } }); @@ -52,7 +51,7 @@ void text() { .get("test") .then() .statusCode(200) - .body(equalTo("test")); + .body(equalTo("text")); } @Test @@ -62,7 +61,7 @@ void html() { .get("test") .then() .statusCode(200) - .body(equalTo("test")); + .body(containsString("")); } @Test @@ -71,7 +70,7 @@ void json() { .when() .get("test") .then() - .statusCode(404); + .statusCode(406); } @Test @@ -82,7 +81,7 @@ void setAcceptToTextInFilter() { .get("test") .then() .statusCode(200) - .body(equalTo("test")); + .body(equalTo("text")); } @Path("/test") From 1ddb1015b0207cec8f5ae76b47ff6f299454705a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 11 Apr 2024 18:50:49 +0300 Subject: [PATCH 09/24] Take MediaType set in pre-match filter into account during serialization Fixes: #40019 (cherry picked from commit 4f86368e4f00984558956fdd5ddac9a240c8c78c) --- .../handlers/VariableProducesHandler.java | 14 ++- .../matching/PreMatchAcceptInHeaderTest.java | 107 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java index 6c1e712350e67..77faad733c928 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java @@ -44,8 +44,18 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti //TODO? return; } - MediaType res = mediaTypeList.negotiateProduces(requestContext.serverRequest().getRequestHeader(HttpHeaders.ACCEPT)) - .getKey(); + MediaType res = null; + List accepts = requestContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT); + for (String accept : accepts) { + res = mediaTypeList.negotiateProduces(accept).getKey(); + if (res != null) { + break; + } + } + if (res == null) { // fallback for some tests + res = mediaTypeList.negotiateProduces(requestContext.serverRequest().getRequestHeader(HttpHeaders.ACCEPT)) + .getKey(); + } if (res == null) { throw new WebApplicationException(Response .notAcceptable(Variant.mediaTypes(mediaTypeList.getSortedMediaTypes()).build()) diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java index eee195df3ce55..16b31cd54b127 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java @@ -4,11 +4,16 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.util.function.Supplier; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; @@ -17,6 +22,9 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.Provider; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -84,6 +92,37 @@ void setAcceptToTextInFilter() { .body(equalTo("text")); } + @Test + void entityJsonWithoutAcceptToTextInFilter() { + given().accept("application/json") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(containsString("\"text\"")); + } + + @Test + void entityTextWithoutAcceptToTextInFilter() { + given().accept("text/plain") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(equalTo("text")); + } + + @Test + void entityTextWithAcceptToTextInFilter() { + given().accept("application/json") + .header("x-set-accept-to-text", "true") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(equalTo("text")); + } + @Path("/test") public static class Resource { @@ -106,6 +145,16 @@ public String html() { """; } + + @GET + @Path("entity") + @Produces({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON }) + public Entity entity() { + return new Entity("text"); + } + } + + public record Entity(String value) { } @PreMatching @@ -120,4 +169,62 @@ public void filter(ContainerRequestContext requestContext) { } } } + + @Provider + @Produces(MediaType.TEXT_PLAIN) + public static class DummyTextMessageBodyWriter implements ServerMessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, + MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeResponse(Object o, Type genericType, ServerRequestContext context) + throws WebApplicationException, IOException { + context.serverResponse().end(((Entity) o).value()); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should not be called"); + } + } + + @Provider + @Produces(MediaType.APPLICATION_JSON) + public static class DummyJsonMessageBodyWriter implements ServerMessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, + MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeResponse(Object o, Type genericType, ServerRequestContext context) + throws WebApplicationException, IOException { + context.serverResponse().end("{\"value\":\"" + ((Entity) o).value() + "\"}"); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should not be called"); + } + } } From 2b49377f5a5461f7ee6f1f3383c862774bb341f8 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 11 Apr 2024 22:51:41 +0200 Subject: [PATCH 10/24] Collect only runtime static resources for native builds (cherry picked from commit 77ec8012d859e52691b82164b24fd118cb6784a9) --- .../deployment/StaticResourcesProcessor.java | 111 +++++++----------- 1 file changed, 41 insertions(+), 70 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java index 11bcd0d08eaf8..4371475cfb77e 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java @@ -2,13 +2,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.FileVisitResult; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -16,17 +10,18 @@ import java.util.Set; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.bootstrap.classloading.ClassPathElement; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; -import io.quarkus.deployment.ApplicationArchive; 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.Record; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; -import io.quarkus.runtime.util.ClassPathUtils; +import io.quarkus.paths.FilteredPathTree; +import io.quarkus.paths.PathFilter; +import io.quarkus.paths.PathVisitor; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem; import io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem; @@ -38,14 +33,14 @@ public class StaticResourcesProcessor { @BuildStep - void collectStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem applicationArchivesBuildItem, + void collectStaticResources(Capabilities capabilities, List additionalStaticResources, - BuildProducer staticResources) throws Exception { + BuildProducer staticResources) { if (capabilities.isPresent(Capability.SERVLET)) { // Servlet container handles static resources return; } - Set paths = getClasspathResources(applicationArchivesBuildItem); + Set paths = getClasspathResources(); for (AdditionalStaticResourceBuildItem bi : additionalStaticResources) { paths.add(new StaticResourcesBuildItem.Entry(bi.getPath(), bi.isDirectory())); } @@ -66,7 +61,7 @@ public void runtimeInit(Optional staticResources, Stat @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) public void nativeImageResource(Optional staticResources, - BuildProducer producer) throws IOException { + BuildProducer producer) { if (staticResources.isPresent()) { Set entries = staticResources.get().getEntries(); List metaInfResources = new ArrayList<>(entries.size()); @@ -83,79 +78,55 @@ public void nativeImageResource(Optional staticResourc // register all directories under META-INF/resources for reflection in order to enable // the serving of index.html in arbitrarily nested directories - ClassPathUtils.consumeAsPaths(StaticResourcesRecorder.META_INF_RESOURCES, resource -> { - try { - Files.walkFileTree(resource, new SimpleFileVisitor<>() { - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { - if (e != null) { - throw e; - } - int index = dir.toString().indexOf(StaticResourcesRecorder.META_INF_RESOURCES); - if (index > 0) { - producer.produce(new NativeImageResourceBuildItem(dir.toString().substring(index))); - } - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - throw new UncheckedIOException(e); + final Set collectedDirs = new HashSet<>(); + visitRuntimeMetaInfResources(visit -> { + if (Files.isDirectory(visit.getPath())) { + final String relativePath = visit.getRelativePath("/"); + if (collectedDirs.add(relativePath)) { + producer.produce(new NativeImageResourceBuildItem(relativePath)); + } } }); - } } /** * Find all static file resources that are available from classpath. * - * @param applicationArchivesBuildItem * @return the set of static resources - * @throws Exception */ - private Set getClasspathResources( - ApplicationArchivesBuildItem applicationArchivesBuildItem) - throws Exception { + private Set getClasspathResources() { Set knownPaths = new HashSet<>(); - - ClassPathUtils.consumeAsPaths(StaticResourcesRecorder.META_INF_RESOURCES, resource -> { - collectKnownPaths(resource, knownPaths); + visitRuntimeMetaInfResources(visit -> { + if (!Files.isDirectory(visit.getPath())) { + knownPaths.add(new StaticResourcesBuildItem.Entry( + visit.getRelativePath("/").substring(StaticResourcesRecorder.META_INF_RESOURCES.length()), + false)); + } }); - - for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) { - i.accept(tree -> { - Path resource = tree.getPath(StaticResourcesRecorder.META_INF_RESOURCES); - if (resource != null && Files.exists(resource)) { - collectKnownPaths(resource, knownPaths); - } - }); - } - return knownPaths; } - private void collectKnownPaths(Path resource, Set knownPaths) { - try { - Files.walkFileTree(resource, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path p, BasicFileAttributes attrs) - throws IOException { - String file = resource.relativize(p).toString(); - // Windows has a backslash - file = file.replace('\\', '/'); - if (!file.startsWith("/")) { - file = "/" + file; - } - - if (QuarkusClassLoader - .isResourcePresentAtRuntime(StaticResourcesRecorder.META_INF_RESOURCES + file)) { - knownPaths.add(new StaticResourcesBuildItem.Entry(file, false)); - } - return FileVisitResult.CONTINUE; + /** + * Visits all {@code META-INF/resources} directories and their content found on the runtime classpath + * + * @param visitor visitor implementation + */ + private static void visitRuntimeMetaInfResources(PathVisitor visitor) { + final List elements = QuarkusClassLoader.getElements(StaticResourcesRecorder.META_INF_RESOURCES, + false); + if (!elements.isEmpty()) { + final PathFilter filter = PathFilter.forIncludes(List.of( + StaticResourcesRecorder.META_INF_RESOURCES + "/**", + StaticResourcesRecorder.META_INF_RESOURCES)); + for (var element : elements) { + if (element.isRuntime()) { + element.apply(tree -> { + new FilteredPathTree(tree, filter).walk(visitor); + return null; + }); } - }); - } catch (IOException e) { - throw new UncheckedIOException(e); + } } } } From c7698251a57073a52ddb3c44c524fd76c50390fb Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 9 Apr 2024 13:02:37 -0300 Subject: [PATCH 11/24] Fix MessageBundle key/file name resolver algorithm (cherry picked from commit 463a411f31d9eea1abd812427437caef91c412a1) --- .../deployment/MessageBundleProcessor.java | 26 ++++++++++- .../MessageBundleProcessorTest.java | 26 +++++++++++ .../qute/deployment/i18n/EmailBundles.java | 45 +++++++++++++++++++ .../i18n/MessageBundleNameCollisionTest.java | 38 ++++++++++++++++ .../messages/EmailBundles_started.properties | 6 +++ .../EmailBundles_startedValidator.properties | 5 +++ ...mailBundles_startedValidator_en.properties | 5 +++ .../EmailBundles_started_en.properties | 6 +++ 8 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/MessageBundleProcessorTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/EmailBundles.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleNameCollisionTest.java create mode 100644 extensions/qute/deployment/src/test/resources/messages/EmailBundles_started.properties create mode 100644 extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator.properties create mode 100644 extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator_en.properties create mode 100644 extensions/qute/deployment/src/test/resources/messages/EmailBundles_started_en.properties diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index a78a3331d11d7..78be8027ee3d0 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -205,7 +205,7 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv Map localeToMergeCandidate = new HashMap<>(); for (Path messageFile : messageFiles) { String fileName = messageFile.getFileName().toString(); - if (fileName.startsWith(name)) { + if (bundleNameMatchesFileName(fileName, name)) { final String locale; int postfixIdx = fileName.indexOf('.'); if (postfixIdx == name.length()) { @@ -315,6 +315,30 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv return bundles; } + static boolean bundleNameMatchesFileName(String fileName, String name) { + int fileSeparatorIdx = fileName.indexOf('.'); + // Remove file extension if exists + if (fileSeparatorIdx > -1) { + fileName = fileName.substring(0, fileSeparatorIdx); + } + // Split the filename and the bundle name by underscores + String[] fileNameParts = fileName.split("_"); + String[] nameParts = name.split("_"); + + if (fileNameParts.length < nameParts.length) { + return false; + } + + // Compare each part of the filename with the corresponding part of the bundle name + for (int i = 0; i < nameParts.length; i++) { + if (!fileNameParts[i].equals(nameParts[i])) { + return false; + } + } + + return true; + } + @Record(value = STATIC_INIT) @BuildStep void initBundleContext(MessageBundleRecorder recorder, diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/MessageBundleProcessorTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/MessageBundleProcessorTest.java new file mode 100644 index 0000000000000..4850f984ef46c --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/MessageBundleProcessorTest.java @@ -0,0 +1,26 @@ +package io.quarkus.qute.deployment; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class MessageBundleProcessorTest { + + @Test + void bundleNameMatchesFileName() { + assertTrue(MessageBundleProcessor.bundleNameMatchesFileName("messages.properties", "messages")); + assertTrue(MessageBundleProcessor.bundleNameMatchesFileName("started.properties", "started")); + assertTrue(MessageBundleProcessor.bundleNameMatchesFileName("startedValidation.properties", "startedValidation")); + assertTrue(MessageBundleProcessor.bundleNameMatchesFileName("EmailBundles_startedValidation.properties", + "EmailBundles_startedValidation")); + assertTrue(MessageBundleProcessor.bundleNameMatchesFileName("EmailBundles_startedValidation_pt_BR.properties", + "EmailBundles_startedValidation")); + + assertFalse(MessageBundleProcessor.bundleNameMatchesFileName("startedValidation.properties", "started")); + assertFalse(MessageBundleProcessor.bundleNameMatchesFileName("EmailBundles_startedValidation.properties", + "EmailBundles_started")); + assertFalse(MessageBundleProcessor.bundleNameMatchesFileName("EmailBundles_startedValidation_pt_BR.properties", + "EmailBundles_started")); + } +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/EmailBundles.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/EmailBundles.java new file mode 100644 index 0000000000000..2f7c89a8d0c19 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/EmailBundles.java @@ -0,0 +1,45 @@ +package io.quarkus.qute.deployment.i18n; + +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; + +public class EmailBundles { + @MessageBundle + interface started { + @Message + String started(String id, String filename); + + @Message + String documentAccessUrl(String url); + + @Message + String nextNotification(); + + @Message + String signingProcessStart(String id, String filename); + + @Message + String subject(String customer, String filename); + + @Message + String signForValidation(); + } + + @MessageBundle + interface startedValidator { + @Message + String started(String id, String filename); + + @Message + String turnEmailWillBeSent(); + + @Message + String youMayAlreadyAccessDocument(); + + @Message + String subject(String customer, String filename); + + @Message + String signForValidation(); + } +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleNameCollisionTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleNameCollisionTest.java new file mode 100644 index 0000000000000..6c1d85c11b4c5 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleNameCollisionTest.java @@ -0,0 +1,38 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Engine; +import io.quarkus.qute.i18n.MessageBundles; +import io.quarkus.test.QuarkusUnitTest; + +public class MessageBundleNameCollisionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .overrideConfigKey("quarkus.default-locale", "en_US") + .withApplicationRoot((jar) -> jar + .addClasses(EmailBundles.class) + .addAsResource("messages/EmailBundles_started.properties") + .addAsResource("messages/EmailBundles_started_en.properties") + .addAsResource("messages/EmailBundles_startedValidator.properties") + .addAsResource("messages/EmailBundles_startedValidator_en.properties")); + + @Inject + Engine engine; + + @Test + public void testBundleMethodIsFound() { + EmailBundles.startedValidator startedValidator = MessageBundles.get(EmailBundles.startedValidator.class); + assertEquals("You will be notified with another email when it is your turn to sign.", + startedValidator.turnEmailWillBeSent()); + assertEquals("You will be notified with another email when it is your turn to sign.", + engine.parse("{EmailBundles_startedValidator:turnEmailWillBeSent()}").render()); + } + +} diff --git a/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started.properties b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started.properties new file mode 100644 index 0000000000000..007c4b2517dc8 --- /dev/null +++ b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started.properties @@ -0,0 +1,6 @@ +started=In this process you will sign the document to validate it. +signingProcessStart=you have started a signing process {id} for document "{filename}". +nextNotification=You will be notified with another email when it is your signing turn. +documentAccessUrl=You may access the document in the following link: +subject=Signing process initiated by {customer} for file {filename}. +signForValidation=In this process you will sign the document to validate it. \ No newline at end of file diff --git a/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator.properties b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator.properties new file mode 100644 index 0000000000000..88f05a121ad14 --- /dev/null +++ b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator.properties @@ -0,0 +1,5 @@ +signForValidation=In this process you will sign the document to validate it. +started=has started a signing process {id} for the document "{filename}". +subject=Signing process initiated by {customer} for file {filename}. +turnEmailWillBeSent=You will be notified with another email when it is your turn to sign. +youMayAlreadyAccessDocument=You can access the document at the following link: diff --git a/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator_en.properties b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator_en.properties new file mode 100644 index 0000000000000..79d65ea85fb73 --- /dev/null +++ b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_startedValidator_en.properties @@ -0,0 +1,5 @@ +signForValidation=In this process you will sign the document to validate it. +started=has started a signing process {id} for the document "{filename}". +subject=Signing process initiated by {customer} for file {filename}. +turnEmailWillBeSent=You will be notified with another email when it is your turn to sign. +youMayAlreadyAccessDocument=You can access the document at the following link: \ No newline at end of file diff --git a/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started_en.properties b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started_en.properties new file mode 100644 index 0000000000000..007c4b2517dc8 --- /dev/null +++ b/extensions/qute/deployment/src/test/resources/messages/EmailBundles_started_en.properties @@ -0,0 +1,6 @@ +started=In this process you will sign the document to validate it. +signingProcessStart=you have started a signing process {id} for document "{filename}". +nextNotification=You will be notified with another email when it is your signing turn. +documentAccessUrl=You may access the document in the following link: +subject=Signing process initiated by {customer} for file {filename}. +signForValidation=In this process you will sign the document to validate it. \ No newline at end of file From 5eff175ad09e099a891c1a04a5fe9f3ea8ab6e0b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 12 Apr 2024 18:32:09 +0300 Subject: [PATCH 12/24] Add a note about FileUpload and FileDownload These types should not have been added to the common module, they should have gone into the server part (cherry picked from commit 7210f5d0ea4f78869ba7e510c19b0e726590f0b4) --- .../org/jboss/resteasy/reactive/multipart/FileDownload.java | 5 +++++ .../org/jboss/resteasy/reactive/multipart/FileUpload.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java index 8a797e2766aee..25f48b6819786 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java @@ -1,4 +1,9 @@ package org.jboss.resteasy.reactive.multipart; +/** + * Represent a file that should be pushed to the client. + *

+ * WARNING: This type is currently only supported on the server + */ public interface FileDownload extends FilePart { } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java index 7576168d51aa6..57a2bc9996ffa 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java @@ -2,6 +2,11 @@ import java.nio.file.Path; +/** + * Represent a file that has been uploaded. + *

+ * WARNING: This type is currently only supported on the server + */ public interface FileUpload extends FilePart { /** From 446ca6994332d82eb76ecf96ae25102485b29103 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 12 Apr 2024 18:29:09 +0300 Subject: [PATCH 13/24] Improve the documentation about sending multipart with the REST Client Co-authored-by: Guillaume Smet (cherry picked from commit 35f322fc8b1352e8ff9bc53fe54a0e7643985e02) --- docs/src/main/asciidoc/rest-client.adoc | 199 ++++++++++++------------ 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 549b2fd6fa886..4df2562e291fe 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -325,106 +325,6 @@ public interface ExtensionsService { } ---- - -=== Using ClientMultipartForm - -MultipartForm can be built using the Class `ClientMultipartForm` which supports building the form as needed: - -`ClientMultipartForm` can be programmatically created with custom inputs and/or from `MultipartFormDataInput` and/or from custom Quarkus REST Input annotated with `@RestForm` if received. - -[source, java] ----- -public interface MultipartService { - - @POST - @Path("/multipart") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Produces(MediaType.APPLICATION_JSON) - Map multipart(ClientMultipartForm dataParts); // <1> -} ----- - -<1> input to the method is a custom Generic `ClientMultipartForm` which matches external application api contract. - - -More information about this Class and supported methods can be found on the javadoc of link:https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-client/latest/org/jboss/resteasy/reactive/client/api/ClientMultipartForm.html[`ClientMultipartForm`]. - - -Build `ClientMultipartForm` from `MultipartFormDataInput` programmatically - -[source, java] ----- -public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) // <1> - throws IOException { - ClientMultipartForm multiPartForm = ClientMultipartForm.create(); // <2> - for (Entry> attribute : inputForm.getValues().entrySet()) { - for (FormValue fv : attribute.getValue()) { - if (fv.isFileItem()) { - final FileItem fi = fv.getFileItem(); - String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE), - MediaType.APPLICATION_OCTET_STREAM); - if (fi.isInMemory()) { - multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), - Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); // <3> - } else { - multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), - fi.getFile().toString(), mediaType); // <4> - } - } else { - multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); // <5> - } - } - } - return multiPartForm; -} ----- - -<1> `MultipartFormDataInput` inputForm supported by Quarkus REST (Server). -<2> Creating a `ClientMultipartForm` object to populate with various dataparts. -<3> Adding InMemory `FileItem` to `ClientMultipartForm` -<4> Adding physical `FileItem` to `ClientMultipartForm` -<5> Adding any attribute directly to `ClientMultipartForm` if not `FileItem`. - -Build `ClientMultipartForm` from custom Attributes annotated with `@RestForm` - -[source, java] ----- -public class MultiPartPayloadFormData { // <1> - - @RestForm("files") - @PartType(MediaType.APPLICATION_OCTET_STREAM) - List files; - - @RestForm("jsonPayload") - @PartType(MediaType.TEXT_PLAIN) - String jsonPayload; -} - -/* - * Generate ClientMultipartForm from custom attributes annotated with @RestForm - */ -public ClientMultipartForm buildClientMultipartForm(MultiPartPayloadFormData inputForm) { // <1> - ClientMultipartForm multiPartForm = ClientMultipartForm.create(); - multiPartForm.attribute("jsonPayload", inputForm.getJsonPayload(), "jsonPayload"); // <2> - inputForm.getFiles().forEach(fu -> { - multiPartForm.binaryFileUpload("file", fu.name(), fu.filePath().toString(), fu.contentType()); // <3> - }); - return multiPartForm; -} ----- - -<1> `MultiPartPayloadFormData` custom Object created to match the API contract for calling service which needs to be converted to `ClientMultipartForm` -<2> Adding attribute `jsonPayload` directly to `ClientMultipartForm` -<3> Adding `FileUpload` objects to `ClientMultipartForm` as binaryFileUpload with contentType. - -[NOTE] -==== -When sending multipart data that uses the same name, problems can arise if the client and server do not use the same multipart encoder mode. -By default, the REST Client uses `RFC1738`, but depending on the situation, clients may need to be configured with `HTML5` or `RFC3986` mode. - -This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property. -==== - === Sending large payloads The REST Client is capable of sending arbitrarily large HTTP bodies without buffering the contents in memory, if one of the following types is used: @@ -1554,6 +1454,105 @@ You can also send JSON multiparts by specifying the `@PartType` annotation: String sendMultipart(@RestForm @PartType(MediaType.APPLICATION_JSON) Person person); ---- +==== Programmatically creating the Multipart form + +In cases where the multipart content needs to be built up programmatically, the REST Client provides `ClientMultipartForm` which can be used in the REST Client like so: + +[source, java] +---- +public interface MultipartService { + + @POST + @Path("/multipart") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + Map multipart(ClientMultipartForm dataParts); +} +---- + + +More information about this class and supported methods can be found on the javadoc of link:https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-client/latest/org/jboss/resteasy/reactive/client/api/ClientMultipartForm.html[`ClientMultipartForm`]. + +===== Converting a received multipart object into a client request + +A good example of creating `ClientMultipartForm` is one where it is created from the server's `MultipartFormDataInput` (which represents a multipart request received by xref:rest.adoc#multipart[Quarkus REST]) - the purpose being to propagate the request downstream while allowing for arbitrary modifications: + +[source, java] +---- +public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) // <1> + throws IOException { + ClientMultipartForm multiPartForm = ClientMultipartForm.create(); // <2> + for (Entry> attribute : inputForm.getValues().entrySet()) { + for (FormValue fv : attribute.getValue()) { + if (fv.isFileItem()) { + final FileItem fi = fv.getFileItem(); + String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE), + MediaType.APPLICATION_OCTET_STREAM); + if (fi.isInMemory()) { + multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), + Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); // <3> + } else { + multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), + fi.getFile().toString(), mediaType); // <4> + } + } else { + multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); // <5> + } + } + } + return multiPartForm; +} +---- + +<1> `MultipartFormDataInput` is a Quarkus REST (Server) type representing a received multipart request. +<2> A `ClientMultipartForm` is created. +<3> `FileItem` attribute is created for the request attribute that represented an in memory file attribute +<4> `FileItem` attribute is created for the request attribute that represented a file attribute saved on the file system +<5> Non-file attributes added directly to `ClientMultipartForm` if not `FileItem`. + + +In a similar fashion if the received server multipart request is known and looks something like: + +[source, java] +---- +public class Request { // <1> + + @RestForm("files") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + List files; + + @RestForm("jsonPayload") + @PartType(MediaType.TEXT_PLAIN) + String jsonPayload; +} +---- + +the `ClientMultipartForm` can be created easily as follows: + +[source, java] +---- +public ClientMultipartForm buildClientMultipartForm(Request request) { // <1> + ClientMultipartForm multiPartForm = ClientMultipartForm.create(); + multiPartForm.attribute("jsonPayload", request.getJsonPayload(), "jsonPayload"); // <2> + request.getFiles().forEach(fu -> { + multiPartForm.binaryFileUpload("file", fu.name(), fu.filePath().toString(), fu.contentType()); // <3> + }); + return multiPartForm; +} +---- + +<1> `Request` representing the request the server parts accepts +<2> A `jsonPayload` attribute is added directly to `ClientMultipartForm` +<3> A `binaryFileUpload` is created from the request's `FileUpload` (which is a Quarkus REST (Server) type used to represent a binary file upload) + +[NOTE] +==== +When sending multipart data that uses the same name, problems can arise if the client and server do not use the same multipart encoder mode. +By default, the REST Client uses `RFC1738`, but depending on the situation, clients may need to be configured with `HTML5` or `RFC3986` mode. + +This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property. +==== + === Receiving Multipart Messages REST Client also supports receiving multipart messages. As with sending, to parse a multipart response, you need to create a class that describes the response data, e.g. From 0a387d9b888e4462a4489319086426f27ea7425d Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sun, 14 Apr 2024 18:31:39 +0900 Subject: [PATCH 14/24] Fix typo in cassandra.adoc (cherry picked from commit 4f6c9b8e4d0d966b9dbad4ef0d468399ccb45e56) --- docs/src/main/asciidoc/cassandra.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/cassandra.adoc b/docs/src/main/asciidoc/cassandra.adoc index 4168a3d906050..42cb2c8ba61f0 100644 --- a/docs/src/main/asciidoc/cassandra.adoc +++ b/docs/src/main/asciidoc/cassandra.adoc @@ -317,7 +317,7 @@ public class FruitDto { The translation to and from JSON is done automatically by the Quarkus REST (formerly RESTEasy Reactive) extension, which is included in this guide's pom.xml file. If you want to add it manually to your application, add the -below snippet to your application's ppm.xml file: +below snippet to your application's pom.xml file: [source,xml] ---- From fa73821f79d31ca2c091180a50e0cb4fda758ac9 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 15 Apr 2024 10:45:36 +0200 Subject: [PATCH 15/24] Qute: fix NativeImageResourceBuildItem registration on Windows - previously, a NativeImageResourceBuildItem with a wrong path was produced for a template located in a nested directory (cherry picked from commit 986ad73d2232b54bc9d597f277276c03961c1aa6) --- .../qute/deployment/QuteProcessor.java | 103 ++++++++++++------ .../AdditionalTemplateRootTest.java | 8 +- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index aacd2d39e48e0..886d87aaff7c7 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -8,7 +8,6 @@ import static java.util.function.Predicate.not; import static java.util.stream.Collectors.toMap; -import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -2149,15 +2148,17 @@ public boolean test(String path) { } for (Path resolvedPath : artifact.getResolvedPaths()) { if (Files.isDirectory(resolvedPath)) { - scanPath(resolvedPath, resolvedPath, config, templateRoots, watchedPaths, templatePaths, + scanRootPath(resolvedPath, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); } else { try (FileSystem artifactFs = ZipUtils.newFileSystem(resolvedPath)) { + // Iterate over template roots, such as "templates", and collect the included templates for (String templateRoot : templateRoots) { Path artifactBasePath = artifactFs.getPath(templateRoot); if (Files.exists(artifactBasePath)) { - LOGGER.debugf("Found extension templates in: %s", resolvedPath); - scan(artifactBasePath, artifactBasePath, templateRoot + "/", watchedPaths, templatePaths, + LOGGER.debugf("Found template root in extension artifact: %s", resolvedPath); + scanDirectory(artifactBasePath, artifactBasePath, templateRoot + "/", watchedPaths, + templatePaths, nativeImageResources, config); } @@ -2173,13 +2174,20 @@ public boolean test(String path) { for (Path root : tree.getRoots()) { // Note that we cannot use ApplicationArchive.getChildPath(String) here because we would not be able to detect // a wrong directory name on case-insensitive file systems - scanPath(root, root, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); + scanRootPath(root, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); } }); } } - private void scanPath(Path rootPath, Path path, QuteConfig config, TemplateRootsBuildItem templateRoots, + private void scanRootPath(Path rootPath, QuteConfig config, TemplateRootsBuildItem templateRoots, + BuildProducer watchedPaths, + BuildProducer templatePaths, + BuildProducer nativeImageResources) { + scanRootPath(rootPath, rootPath, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); + } + + private void scanRootPath(Path rootPath, Path path, QuteConfig config, TemplateRootsBuildItem templateRoots, BuildProducer watchedPaths, BuildProducer templatePaths, BuildProducer nativeImageResources) { @@ -2193,15 +2201,15 @@ private void scanPath(Path rootPath, Path path, QuteConfig config, TemplateRoots // "/io", "/META-INF", "/templates", "/web", etc. Path relativePath = rootPath.relativize(file); if (templateRoots.isRoot(relativePath)) { - LOGGER.debugf("Found templates dir: %s", file); - // The base path is an OS-specific path relative to the template root - String basePath = relativePath.toString() + File.separatorChar; - scan(file, file, basePath, watchedPaths, templatePaths, + LOGGER.debugf("Found templates root dir: %s", file); + // The base path is an OS-specific template root path relative to the scanned root path + String basePath = relativePath.toString() + relativePath.getFileSystem().getSeparator(); + scanDirectory(file, file, basePath, watchedPaths, templatePaths, nativeImageResources, config); } else if (templateRoots.maybeRoot(relativePath)) { // Scan the path recursively because the template root may be nested, for example "/web/public" - scanPath(rootPath, file, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); + scanRootPath(rootPath, file, config, templateRoots, watchedPaths, templatePaths, nativeImageResources); } } } @@ -3384,33 +3392,54 @@ public static String getName(InjectionPointInfo injectionPoint) { throw new IllegalArgumentException(); } + /** + * + * @param templatePaths + * @param watchedPaths + * @param nativeImageResources + * @param osSpecificResourcePath The OS-specific resource path, i.e. templates\nested\foo.html + * @param templatePath The path relative to the template root; using the {@code /} path separator + * @param originalPath + * @param config + */ private static void produceTemplateBuildItems(BuildProducer templatePaths, BuildProducer watchedPaths, - BuildProducer nativeImageResources, String basePath, String filePath, + BuildProducer nativeImageResources, String osSpecificResourcePath, + String templatePath, Path originalPath, QuteConfig config) { - if (filePath.isEmpty()) { + if (templatePath.isEmpty()) { return; } - // OS-specific full path, i.e. templates\foo.html - String osSpecificPath = basePath + filePath; // OS-agnostic full path, i.e. templates/foo.html - String osAgnosticPath = osSpecificPath; - if (File.separatorChar != '/') { - osAgnosticPath = osAgnosticPath.replace(File.separatorChar, '/'); - } - LOGGER.debugf("Produce template build items [filePath: %s, fullPath: %s, originalPath: %s", filePath, osSpecificPath, + String osAgnosticResourcePath = toOsAgnosticPath(osSpecificResourcePath, originalPath.getFileSystem()); + LOGGER.debugf("Produce template build items [templatePath: %s, osSpecificResourcePath: %s, originalPath: %s", + templatePath, + osSpecificResourcePath, originalPath); boolean restartNeeded = true; if (config.devMode.noRestartTemplates.isPresent()) { - restartNeeded = !config.devMode.noRestartTemplates.get().matcher(osAgnosticPath).matches(); + restartNeeded = !config.devMode.noRestartTemplates.get().matcher(osAgnosticResourcePath).matches(); } - watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(osAgnosticPath, restartNeeded)); - nativeImageResources.produce(new NativeImageResourceBuildItem(osSpecificPath)); + watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(osAgnosticResourcePath, restartNeeded)); + nativeImageResources.produce(new NativeImageResourceBuildItem(osSpecificResourcePath)); templatePaths.produce( - new TemplatePathBuildItem(filePath, originalPath, readTemplateContent(originalPath, config.defaultCharset))); + new TemplatePathBuildItem(templatePath, originalPath, + readTemplateContent(originalPath, config.defaultCharset))); } - private void scan(Path root, Path directory, String basePath, BuildProducer watchedPaths, + /** + * + * @param root + * @param directory + * @param basePath OS-specific template root path relative to the scanned root path, e.g. {@code templates/} + * @param watchedPaths + * @param templatePaths + * @param nativeImageResources + * @param config + * @throws IOException + */ + private void scanDirectory(Path root, Path directory, String basePath, + BuildProducer watchedPaths, BuildProducer templatePaths, BuildProducer nativeImageResources, QuteConfig config) @@ -3431,24 +3460,36 @@ private void scan(Path root, Path directory, String basePath, BuildProducer> excludes) { for (Predicate exclude : excludes) { if (exclude.test(check)) { diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java index 7e26be68d7834..9095a01599387 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templateroot/AdditionalTemplateRootTest.java @@ -28,6 +28,7 @@ public class AdditionalTemplateRootTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot(root -> root .addAsResource(new StringAsset("Hi {name}!"), "templates/hi.txt") + .addAsResource(new StringAsset("Hoho {name}!"), "templates/nested/hoho.txt") .addAsResource(new StringAsset("Hello {name}!"), "web/public/hello.txt")) .addBuildChainCustomizer(buildCustomizer()); @@ -52,11 +53,13 @@ public void execute(BuildContext context) { if (item.getResources().contains("web/public/hello.txt") || item.getResources().contains("web\\public\\hello.txt") || item.getResources().contains("templates/hi.txt") - || item.getResources().contains("templates\\hi.txt")) { + || item.getResources().contains("templates\\hi.txt") + || item.getResources().contains("templates/nested/hoho.txt") + || item.getResources().contains("templates\\nested\\hoho.txt")) { found++; } } - if (found != 2) { + if (found != 3) { throw new IllegalStateException(items.stream().flatMap(i -> i.getResources().stream()) .collect(Collectors.toList()).toString()); } @@ -79,6 +82,7 @@ public void execute(BuildContext context) { public void testTemplate() { assertEquals("Hi M!", engine.getTemplate("hi").data("name", "M").render()); assertEquals("Hello M!", hello.data("name", "M").render()); + assertEquals("Hoho M!", engine.getTemplate("nested/hoho").data("name", "M").render()); } } From 759b01db4860843785335204a8e9e5adceae1aad Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 15 Apr 2024 18:11:25 +0200 Subject: [PATCH 16/24] Rewrite footnotes for downstream documentation The modern version of footnotes is not supported in the downstream tooling and we can't use the older version in our doc as it triggers warnings. Thus we rewrite the new format to the old format specifically for downstream doc. (cherry picked from commit 82f7b20709255ea7e08aa8237eee0aba41903a91) --- .../AssembleDownstreamDocumentation.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java b/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java index f2438f7f08a56..6f23fd809cd4c 100755 --- a/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java +++ b/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java @@ -61,6 +61,7 @@ public class AssembleDownstreamDocumentation { Pattern.CASE_INSENSITIVE + Pattern.MULTILINE); private static final String SOURCE_BLOCK_PREFIX = "[source"; private static final String SOURCE_BLOCK_DELIMITER = "--"; + private static final Pattern FOOTNOTE_PATTERN = Pattern.compile("footnote:([a-z0-9_-]+)\\[(\\])?"); private static final String PROJECT_NAME_ATTRIBUTE = "{project-name}"; private static final String RED_HAT_BUILD_OF_QUARKUS = "Red Hat build of Quarkus"; @@ -386,7 +387,7 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set d if (currentBuffer.length() > 0) { rewrittenGuide.append( - rewriteLinks(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides, + rewriteContent(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides, titlesByReference, linkRewritingErrors)); currentBuffer.setLength(0); } @@ -399,7 +400,7 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set d if (currentBuffer.length() > 0) { rewrittenGuide.append( - rewriteLinks(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides, + rewriteContent(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides, titlesByReference, linkRewritingErrors)); } @@ -413,7 +414,7 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set d Files.writeString(targetFile, rewrittenGuideWithoutTabs.trim()); } - private static String rewriteLinks(String fileName, + private static String rewriteContent(String fileName, String content, Set downstreamGuides, Map titlesByReference, @@ -454,6 +455,14 @@ private static String rewriteLinks(String fileName, return "[[" + mr.group(1) + "]]"; }); + content = FOOTNOTE_PATTERN.matcher(content).replaceAll(mr -> { + if (mr.group(2) != null) { + return "footnoteref:[" + mr.group(1) + "]"; + } + + return "footnoteref:[" + mr.group(1) + ", "; + }); + return content; } From 0a5d314d0096f31c3ff5ce23240fca55b4dce013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bresson?= Date: Mon, 15 Apr 2024 14:50:27 +0200 Subject: [PATCH 17/24] docs: mention logging in smallrye-graphql-client (cherry picked from commit 8b1094ec0a2fc850366e83e0f5c5fd5e65af1ab2) --- docs/src/main/asciidoc/smallrye-graphql-client.adoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/src/main/asciidoc/smallrye-graphql-client.adoc b/docs/src/main/asciidoc/smallrye-graphql-client.adoc index de25e13e8ff09..4aa0a118e0d6b 100644 --- a/docs/src/main/asciidoc/smallrye-graphql-client.adoc +++ b/docs/src/main/asciidoc/smallrye-graphql-client.adoc @@ -200,6 +200,7 @@ public class Planet { Now that we have the model classes, we can create the interface that represents the actual set of operations we want to call on the remote GraphQL service. +[source,java] ---- @GraphQLClientApi(configKey = "star-wars-typesafe") public interface StarWarsClientApi { @@ -256,6 +257,18 @@ With this REST endpoint included in your application, you can simply send a GET and the application will use an injected typesafe client instance to call the remote service, obtain the films and planets, and return the JSON representation of the resulting list. +=== Logging + +For debugging purpose, it is possible to log the request generated by the typesafe client and the response sent back by the server by changing the log level of the `io.smallrye.graphql.client` category to `TRACE` (see the xref:logging.adoc#configure-the-log-level-category-and-format[Logging guide] for more details about how to configure logging). + +This can be achieved by adding the following lines to the `application.properties`: + +[source,properties] +---- +quarkus.log.category."io.smallrye.graphql.client".level=TRACE +quarkus.log.category."io.smallrye.graphql.client".min-level=TRACE +---- + == Using the Dynamic client For the dynamic client, the model classes are optional, because we can work with abstract From ed0f461735eafea550dd448b02f7d22687f82cf5 Mon Sep 17 00:00:00 2001 From: Thomas Canava Date: Sat, 13 Apr 2024 00:27:26 +0200 Subject: [PATCH 18/24] Fix datasource devservices restarting quarkusio/quarkus#40015 (cherry picked from commit 2de7152c58609ccf01818adc16127605905e03aa) --- .../deployment/devservices/DevServicesDatasourceProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java index 559fcbe6fa3d0..a5ab0f15c4e3e 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java @@ -316,6 +316,8 @@ private RunningDevService startDevDb( e.getValue()); } } + setDataSourceProperties(propertiesMap, dbName, devServicesPrefix + "reuse", + String.valueOf(dataSourceBuildTimeConfig.devservices().reuse())); Map devDebProperties = new HashMap<>(); for (DevServicesDatasourceConfigurationHandlerBuildItem devDbConfigurationHandlerBuildItem : configHandlers) { From 29dd724aedab4bb4e0a86bc89d177cd748ad1e26 Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Fri, 12 Apr 2024 10:55:57 +0200 Subject: [PATCH 19/24] Updates to Infinispan 15.0.1.Final (cherry picked from commit a3291897a287b63f3f36661f5209016668bbe1de) --- bom/application/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index c7649f78fd629..525a73b416834 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -141,8 +141,8 @@ 5.4.0 2.2 5.10.2 - 15.0.0.Final - 5.0.1.Final + 15.0.1.Final + 5.0.2.Final 3.1.5 4.1.108.Final 1.16.0 From a063ebd7e4cc66c18ab02910294a46d5434c8edd Mon Sep 17 00:00:00 2001 From: Antonio Musarra Date: Tue, 16 Apr 2024 14:18:03 +0200 Subject: [PATCH 20/24] Update deploying-to-openshift.adoc Replace the build option -Dquarkus.kubernetes.deploy=true with -Dquarkus.openshift.deploy=true (cherry picked from commit af00b5f1a670f705492b92898ee6f80f4c57697a) --- docs/src/main/asciidoc/deploying-to-openshift.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/deploying-to-openshift.adoc b/docs/src/main/asciidoc/deploying-to-openshift.adoc index 09eb23d61ae75..b4500631fa757 100644 --- a/docs/src/main/asciidoc/deploying-to-openshift.adoc +++ b/docs/src/main/asciidoc/deploying-to-openshift.adoc @@ -86,7 +86,7 @@ You can trigger a build and deployment in a single step or build the container i To trigger a build and deployment in a single step: -:build-additional-parameters: -Dquarkus.kubernetes.deploy=true +:build-additional-parameters: -Dquarkus.openshift.deploy=true include::{includes}/devtools/build.adoc[] :!build-additional-parameters: From 3023a1f1a459854b428ff19abf266988ae5c681d Mon Sep 17 00:00:00 2001 From: Jakub Jedlicka Date: Tue, 16 Apr 2024 14:54:16 +0200 Subject: [PATCH 21/24] Update datasource yaml config in docs (cherry picked from commit e430046c26d0ad1a48146351456798932b4957f1) --- docs/src/main/asciidoc/config-yaml.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/config-yaml.adoc b/docs/src/main/asciidoc/config-yaml.adoc index 074132c8bc895..8d420476d9517 100644 --- a/docs/src/main/asciidoc/config-yaml.adoc +++ b/docs/src/main/asciidoc/config-yaml.adoc @@ -79,7 +79,8 @@ quarkus: ---- quarkus: datasource: - url: jdbc:postgresql://localhost:5432/quarkus_test + jdbc: + url: jdbc:postgresql://localhost:5432/quarkus_test hibernate-orm: database: From 0cf9684d271060175dbf8e611ad335b46bbbe775 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 16 Apr 2024 09:49:06 +0200 Subject: [PATCH 22/24] ArC: skip warning about invalid startup for producer methods - resolves #40083 (cherry picked from commit 2c0d16b2b2c7e6a865a5f626256cf24079abf169) --- .../java/io/quarkus/arc/deployment/StartupBuildSteps.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java index 477eee7c6d506..53923c2136643 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java @@ -149,8 +149,12 @@ void registerStartupObservers(ObserverRegistrationPhaseBuildItem observerRegistr && !annotationStore.hasAnnotation(method, DotNames.PRODUCES)) { startupMethods.add(method); } else { - LOG.warnf("Ignored an invalid @Startup method declared on %s: %s", method.declaringClass().name(), - method); + if (!annotationStore.hasAnnotation(method, DotNames.PRODUCES)) { + // Producer methods annotated with @Startup are valid and processed above + LOG.warnf("Ignored an invalid @Startup method declared on %s: %s", + method.declaringClass().name(), + method); + } } } } From 40abe4822ce07ff745433cc0ea39d3ca40a5f9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mal=C3=A9=C5=99?= Date: Mon, 15 Apr 2024 14:30:11 +0200 Subject: [PATCH 23/24] Applying the QE feedback to the Logging guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michal Maléř (cherry picked from commit 561212eb4375769e316a10c9d8d4cd03e6fd6d8f) --- docs/src/main/asciidoc/logging.adoc | 31 +++++++++++-------- .../json/runtime/AdditionalFieldConfig.java | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 0f6ab858131f0..84bef6c0cc3d1 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -74,6 +74,7 @@ The same flow can be applied with any of the <>. - -Configure the runtime logging in the `application.properties` file. +JBoss Logging, integrated into Quarkus, offers a unified configuration for all <> through a single configuration file that sets up all available extensions. +To adjust runtime logging, modify the `application.properties` file. .An example of how you can set the default log level to `INFO` logging and include Hibernate `DEBUG` logs: [source, properties] @@ -347,9 +349,9 @@ The logging format string supports the following symbols: |%t|Thread name|Render the thread name. |%t{id}|Thread ID|Render the thread ID. |%z{}|Time zone|Set the time zone of the output to ``. -|%X{}|Mapped Diagnostic Context Value|Renders the value from Mapped Diagnostic Context -|%X|Mapped Diagnostic Context Values|Renders all the values from Mapped Diagnostic Context in format {property.key=property.value} -|%x|Nested Diagnostics context values|Renders all the values from Nested Diagnostics Context in format {value1.value2} +|%X{}|Mapped Diagnostic Context Value|Renders the value from Mapped Diagnostic Context. +|%X|Mapped Diagnostic Context Values|Renders all the values from Mapped Diagnostic Context in format `{property.key=property.value}`. +|%x|Nested Diagnostics context values|Renders all the values from Nested Diagnostics Context in format `{value1.value2}`. |=== @@ -364,8 +366,8 @@ Changing the console log format is useful, for example, when the console output The `quarkus-logging-json` extension may be employed to add support for the JSON logging format and its related configuration. -Add this extension to your build file as the following snippet illustrates: - +. Add this extension to your build file as the following snippet illustrates: ++ [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml ---- @@ -374,20 +376,21 @@ Add this extension to your build file as the following snippet illustrates: quarkus-logging-json ---- - ++ [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- implementation("io.quarkus:quarkus-logging-json") ---- - ++ By default, the presence of this extension replaces the output format configuration from the console configuration, and the format string and the color settings (if any) are ignored. The other console configuration items, including those controlling asynchronous logging and the log level, will continue to be applied. - ++ For some, it will make sense to use humanly readable (unstructured) logging in dev mode and JSON logging (structured) in production mode. This can be achieved using different profiles, as shown in the following configuration. - -.Disable JSON logging in application.properties for dev and test mode ++ +. Disable JSON logging in application.properties for dev and test mode: ++ [source, properties] ---- %dev.quarkus.log.console.json=false @@ -514,6 +517,8 @@ To register a logging filter: .An example of writing a filter: [source,java] ---- +package com.example; + import io.quarkus.logging.LoggingFilter; import java.util.logging.Filter; import java.util.logging.LogRecord; diff --git a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/AdditionalFieldConfig.java b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/AdditionalFieldConfig.java index ec95d7fb5a59c..dba33ea8e0d72 100644 --- a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/AdditionalFieldConfig.java +++ b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/AdditionalFieldConfig.java @@ -16,7 +16,7 @@ public class AdditionalFieldConfig { /** * Additional field type specification. - * Supported types: string, int, long + * Supported types: {@code string}, {@code int}, and {@code long}. * String is the default if not specified. */ @ConfigItem(defaultValue = "string") From 1485e973ff08b96110bb9260bfd89854854beee3 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 10 Apr 2024 21:19:34 -0300 Subject: [PATCH 24/24] Unsign modified dependency JARs when filtering Add test for JarResultBuildStep#filterJarFile (cherry picked from commit 7e229d433f5bb81a1ec1103678102a97dcebc562) --- core/deployment/pom.xml | 24 +++++++ .../pkg/steps/JarResultBuildStep.java | 64 ++++++++++++------- .../pkg/steps/JarResultBuildStepTest.java | 34 ++++++++++ 3 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java diff --git a/core/deployment/pom.xml b/core/deployment/pom.xml index 0878028369f03..852e752a75c70 100644 --- a/core/deployment/pom.xml +++ b/core/deployment/pom.xml @@ -143,6 +143,30 @@ + + maven-dependency-plugin + + + download-signed-jar + generate-test-resources + + copy + + + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache + 6.9.0.202403050737-r + jar + signed.jar + + + ${project.build.testOutputDirectory} + + + + maven-surefire-plugin diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index 89521eee66ef3..5b77b68fb79fb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -5,7 +5,6 @@ import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; @@ -42,12 +41,12 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; import org.jboss.logging.Logger; @@ -92,14 +91,14 @@ /** * This build step builds both the thin jars and uber jars. - * + *

* The way this is built is a bit convoluted. In general, we only want a single one built, * as determined by the {@link PackageConfig} (unless the config explicitly asks for both of them) - * + *

* However, we still need an extension to be able to ask for a specific one of these despite the config, * e.g. if a serverless environment needs an uberjar to build its deployment package then we need * to be able to provide this. - * + *

* To enable this we have two build steps that strongly produce the respective artifact type build * items, but not a {@link ArtifactResultBuildItem}. We then * have another two build steps that only run if they are configured to consume these explicit @@ -931,7 +930,7 @@ private void copyDependency(Set parentFirstArtifacts, OutputTargetB } else { // we copy jars for which we remove entries to the same directory // which seems a bit odd to me - filterZipFile(resolvedDep, targetPath, removedFromThisArchive); + filterJarFile(resolvedDep, targetPath, removedFromThisArchive); } } } @@ -1125,7 +1124,7 @@ private void copyLibraryJars(FileSystem runnerZipFs, OutputTargetBuildItem outpu + resolvedDep.getFileName(); final Path targetPath = libDir.resolve(fileName); classPath.append(" lib/").append(fileName); - filterZipFile(resolvedDep, targetPath, transformedFromThisArchive); + filterJarFile(resolvedDep, targetPath, transformedFromThisArchive); } } else { // This case can happen when we are building a jar from inside the Quarkus repository @@ -1240,16 +1239,26 @@ private void handleParent(FileSystem runnerZipFs, String fileName, Map transformedFromThisArchive) { - + static void filterJarFile(Path resolvedDep, Path targetPath, Set transformedFromThisArchive) { try { byte[] buffer = new byte[10000]; - try (ZipFile in = new ZipFile(resolvedDep.toFile())) { - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(targetPath.toFile()))) { - Enumeration entries = in.entries(); + try (JarFile in = new JarFile(resolvedDep.toFile(), false)) { + Manifest manifest = in.getManifest(); + if (manifest != null) { + // Remove signature entries + manifest.getEntries().clear(); + } else { + manifest = new Manifest(); + } + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(targetPath), manifest)) { + Enumeration entries = in.entries(); while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (!transformedFromThisArchive.contains(entry.getName())) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + if (!transformedFromThisArchive.contains(entryName) + && !entryName.equals(JarFile.MANIFEST_NAME) + && !entryName.equals("META-INF/INDEX.LIST") + && !isSignatureFile(entryName)) { entry.setCompressedSize(-1); out.putNextEntry(entry); try (InputStream inStream = in.getInputStream(entry)) { @@ -1258,6 +1267,8 @@ private void filterZipFile(Path resolvedDep, Path targetPath, Set transf out.write(buffer, 0, r); } } + } else { + log.debugf("Removed %s from %s", entryName, resolvedDep); } } } @@ -1265,10 +1276,21 @@ private void filterZipFile(Path resolvedDep, Path targetPath, Set transf Files.setLastModifiedTime(targetPath, Files.getLastModifiedTime(resolvedDep)); } } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } + private static boolean isSignatureFile(String entry) { + entry = entry.toUpperCase(); + if (entry.startsWith("META-INF/") && entry.indexOf('/', "META-INF/".length()) == -1) { + return entry.endsWith(".SF") + || entry.endsWith(".DSA") + || entry.endsWith(".RSA") + || entry.endsWith(".EC"); + } + return false; + } + /** * Manifest generation is quite simple : we just have to push some attributes in manifest. * However, it gets a little more complex if the manifest preexists. @@ -1612,12 +1634,8 @@ public boolean downloadIfNecessary() { "https://repo.maven.apache.org/maven2/org/vineflower/vineflower/%s/vineflower-%s.jar", context.versionStr, context.versionStr); try (BufferedInputStream in = new BufferedInputStream(new URL(downloadURL).openStream()); - FileOutputStream fileOutputStream = new FileOutputStream(decompilerJar.toFile())) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - } + OutputStream fileOutputStream = Files.newOutputStream(decompilerJar)) { + in.transferTo(fileOutputStream); return true; } catch (IOException e) { log.error("Unable to download Vineflower from " + downloadURL, e); diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java new file mode 100644 index 0000000000000..7cfb2c4ece496 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java @@ -0,0 +1,34 @@ +package io.quarkus.deployment.pkg.steps; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Test for {@link JarResultBuildStep} + */ +class JarResultBuildStepTest { + + @Test + void should_unsign_jar_when_filtered(@TempDir Path tempDir) throws Exception { + Path signedJarFilePath = Path.of(getClass().getClassLoader().getResource("signed.jar").toURI()); + Path jarFilePath = tempDir.resolve("unsigned.jar"); + JarResultBuildStep.filterJarFile(signedJarFilePath, jarFilePath, + Set.of("org/eclipse/jgit/transport/sshd/SshdSessionFactory.class")); + try (JarFile jarFile = new JarFile(jarFilePath.toFile())) { + assertThat(jarFile.stream().map(JarEntry::getName)).doesNotContain("META-INF/ECLIPSE_.RSA", "META-INF/ECLIPSE_.SF"); + // Check that the manifest is still present + Manifest manifest = jarFile.getManifest(); + assertThat(manifest.getMainAttributes()).isNotEmpty(); + assertThat(manifest.getEntries()).isEmpty(); + } + } + +}