diff --git a/adr/0001-community-discussions.adoc b/adr/0001-community-discussions.adoc index fbbfe4462c5e7..5708ac9269b74 100644 --- a/adr/0001-community-discussions.adoc +++ b/adr/0001-community-discussions.adoc @@ -41,7 +41,7 @@ User looking for a job involving Quarkus - where do he go look ? Not proposed since it is just another waterhose of info with no good ability to ignore/consume as needed/wanted. === enable discussions on github.com/quarkusio/quarkus-insights -Considered as quarkus insights is an existing "community" and let us have separate permissons for moderators/triage but some suggested it might be hard to find. +Considered as quarkus insights is an existing "community" and let us have separate permissions for moderators/triage but some suggested it might be hard to find. === enable discussions on github.com/quarkusio/community Downside is that labels etc. would need to be kept in sync and that searches in github.com/quarkusio would not show up community/discussions. diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 77cde3e59b186..2b182a6aab0e7 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -51,8 +51,8 @@ 1.2.2 1.0.13 2.7.0 - 2.26.0 - 3.18.0 + 2.27.0 + 3.20.0 1.1.2 1.2.1 1.3.5 @@ -88,9 +88,9 @@ 3.12.0 1.15 1.5.1 - 5.6.11.Final + 5.6.12.Final 1.12.9 - 1.1.7.Final + 1.1.8.Final 6.2.5.Final 6.1.7.Final 5.13.1.Alpha1 @@ -109,7 +109,7 @@ 1.0.1.Final 1.20.1.Final 3.4.3.Final - 4.3.3 + 4.3.4 4.5.13 4.4.15 @@ -118,7 +118,7 @@ 2.3.2 2.1.214 42.5.0 - 3.0.7 + 3.0.8 8.0.30 11.2.0.jre11 1.6.7 @@ -127,10 +127,10 @@ 11.5.7.0 1.2.6 4.5.1 - 5.9.0 + 5.9.1 1.5.0 6.14.2 - 13.0.10.Final + 13.0.11.Final 4.4.3.Final 2.9.3 4.1.79.Final @@ -156,10 +156,10 @@ 3.2.0 4.2.0 1.0.10 - 9.3.0 + 9.3.1 1.0.11 - 4.16.0 - 1.32 + 4.16.1 + 1.33 6.0.0 4.7.1 1.5.2 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 2a9d7f36089c1..eb50b9cd424df 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -701,6 +701,11 @@ asciidoctor-maven-plugin ${asciidoctor-maven-plugin.version} + + de.thetaphi + forbiddenapis + ${forbiddenapis-maven-plugin.version} + diff --git a/core/deployment/banned-signatures.txt b/core/deployment/banned-signatures.txt new file mode 100644 index 0000000000000..447cdd1634461 --- /dev/null +++ b/core/deployment/banned-signatures.txt @@ -0,0 +1,3 @@ +@defaultMessage Don't use Maven classes. They won't be available when using Gradle. +org.apache.maven.** +org.codehaus.plexus.** \ No newline at end of file diff --git a/core/deployment/pom.xml b/core/deployment/pom.xml index 18a27d767cca7..f87114058b104 100644 --- a/core/deployment/pom.xml +++ b/core/deployment/pom.xml @@ -196,6 +196,28 @@ + + de.thetaphi + forbiddenapis + + + verify-forbidden-apis + + + false + + ./banned-signatures.txt + + false + true + + compile + + check + + + + diff --git a/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java index e54d05c735570..bdacdd8419c53 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java @@ -25,4 +25,18 @@ public class DebugConfig { */ @ConfigItem Optional generatedClassesDir; + + /** + * If set to a directory, all transformed classes (e.g. Panache entities) will be written into that directory + */ + @ConfigItem + Optional transformedClassesDir; + + /** + * If set to a directory, ZIG files for generated code will be written into that directory. + *

+ * A ZIG file is a textual representation of the generated code that is referenced in the stacktraces. + */ + @ConfigItem + Optional generatedSourcesDir; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index ca3519a4be75a..dba8f2b5eeb88 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -57,6 +57,7 @@ public class QuarkusAugmentor { private final Path targetDir; private final ApplicationModel effectiveModel; private final String baseName; + private final String originalBaseName; private final boolean rebuild; private final boolean auxiliaryApplication; private final Optional auxiliaryDevModeType; @@ -75,6 +76,7 @@ public class QuarkusAugmentor { this.targetDir = builder.targetDir; this.effectiveModel = builder.effectiveModel; this.baseName = builder.baseName; + this.originalBaseName = builder.originalBaseName; this.deploymentClassLoader = builder.deploymentClassLoader; this.rebuild = builder.rebuild; this.devModeType = builder.devModeType; @@ -149,7 +151,7 @@ public BuildResult run() throws Exception { .produce(new LaunchModeBuildItem(launchMode, devModeType == null ? Optional.empty() : Optional.of(devModeType), auxiliaryApplication, auxiliaryDevModeType, test)) - .produce(new BuildSystemTargetBuildItem(targetDir, baseName, rebuild, + .produce(new BuildSystemTargetBuildItem(targetDir, baseName, originalBaseName, rebuild, buildSystemProperties == null ? new Properties() : buildSystemProperties)) .produce(new AppModelProviderBuildItem(effectiveModel)); for (PathCollection i : additionalApplicationArchives) { @@ -194,6 +196,8 @@ public static Builder builder() { public static final class Builder { + private static final String QUARKUS_APPLICATION = "quarkus-application"; + public DevModeType auxiliaryDevModeType; boolean rebuild; List additionalApplicationArchives = new ArrayList<>(); @@ -208,7 +212,8 @@ public static final class Builder { Properties buildSystemProperties; ApplicationModel effectiveModel; - String baseName = "quarkus-application"; + String baseName = QUARKUS_APPLICATION; + String originalBaseName = QUARKUS_APPLICATION; ClassLoader deploymentClassLoader; DevModeType devModeType; boolean test; @@ -302,6 +307,11 @@ public Builder setBaseName(String baseName) { return this; } + public Builder setOriginalBaseName(String originalBaseName) { + this.originalBaseName = originalBaseName; + return this; + } + public Properties getBuildSystemProperties() { return buildSystemProperties; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceBundleBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceBundleBuildItem.java index d486ee0f7dd19..520e81ea8c051 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceBundleBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceBundleBuildItem.java @@ -8,12 +8,23 @@ public final class NativeImageResourceBundleBuildItem extends MultiBuildItem { private final String bundleName; + private final String moduleName; public NativeImageResourceBundleBuildItem(String bundleName) { this.bundleName = bundleName; + this.moduleName = null; + } + + public NativeImageResourceBundleBuildItem(String bundleName, String moduleName) { + this.bundleName = bundleName; + this.moduleName = moduleName; } public String getBundleName() { return bundleName; } + + public String getModuleName() { + return moduleName; + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java index 318a4c8e544e9..2f0a5402a4bc6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java @@ -22,8 +22,6 @@ import java.util.function.Consumer; import java.util.function.Predicate; -import org.apache.maven.shared.utils.cli.CommandLineException; -import org.apache.maven.shared.utils.cli.CommandLineUtils; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import org.jboss.logmanager.formatters.ColorPatternFormatter; @@ -47,6 +45,7 @@ import io.quarkus.deployment.dev.testing.MessageFormat; import io.quarkus.deployment.dev.testing.TestSupport; import io.quarkus.deployment.steps.ClassTransformingBuildStep; +import io.quarkus.deployment.util.CommandLineUtil; import io.quarkus.dev.appstate.ApplicationStartException; import io.quarkus.dev.console.DevConsoleManager; import io.quarkus.dev.spi.DeploymentFailedStartHandler; @@ -122,8 +121,8 @@ public void accept(Integer integer) { public void accept(String args) { try { context.setArgs( - CommandLineUtils.translateCommandline(args)); - } catch (CommandLineException e) { + CommandLineUtil.translateCommandline(args)); + } catch (Exception e) { log.error("Failed to parse command line", e); return; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java index eced94cb17d69..7108a534536b0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java @@ -28,11 +28,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.maven.shared.utils.cli.CommandLineUtils; import org.jboss.logging.Logger; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; +import io.quarkus.deployment.util.CommandLineUtil; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.runtime.logging.JBossVersion; import io.quarkus.runtime.util.JavaVersionUtil; @@ -476,7 +476,7 @@ protected void prepare() throws Exception { args.add("-jar"); args.add(tempFile.getAbsolutePath()); if (applicationArgs != null) { - args.addAll(Arrays.asList(CommandLineUtils.translateCommandline(applicationArgs))); + args.addAll(Arrays.asList(CommandLineUtil.translateCommandline(applicationArgs))); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index e1c0873b86691..d9326e67bb9b9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -53,6 +53,7 @@ import org.jboss.jandex.Indexer; import org.jboss.logging.Logger; +import io.quarkus.bootstrap.runner.DevModeMediator; import io.quarkus.bootstrap.runner.Timing; import io.quarkus.changeagent.ClassChangeAgent; import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; @@ -602,6 +603,7 @@ public Set syncState(Map fileHashes) { ret.add(i.getKey()); } } + List removedFiles = List.of(); for (Map.Entry remaining : ourHashes.entrySet()) { String file = remaining.getKey(); if (file.endsWith("META-INF/MANIFEST.MF") || file.contains("META-INF/maven") @@ -609,8 +611,14 @@ public Set syncState(Map fileHashes) { //we have some filters, for files that we don't want to delete continue; } - log.info("Deleting removed file " + file); - Files.deleteIfExists(applicationRoot.resolve(file)); + log.info("Scheduled for removal " + file); + if (removedFiles.isEmpty()) { + removedFiles = new ArrayList<>(); + } + removedFiles.add(applicationRoot.resolve(file)); + } + if (!removedFiles.isEmpty()) { + DevModeMediator.removedFiles.addLast(removedFiles); } return ret; } catch (IOException e) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java index 4dcaca5be140b..55ac76fb2f44e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java @@ -102,6 +102,9 @@ ApplicationArchivesBuildItem build( removedResources.put(new GACT(entry.getKey().split(":")), entry.getValue()); } + // Add resources removed from the classpath by extensions + removedResources.putAll(curateOutcomeBuildItem.getApplicationModel().getRemovedResources()); + List applicationArchives = scanForOtherIndexes(buildCloseables, appMarkers, root, additionalApplicationArchiveBuildItem, indexDependencyBuildItems, indexCache, curateOutcomeBuildItem, removedResources); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java index 2030fca7c05cb..e00452508859e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java @@ -241,13 +241,13 @@ public Index apply(PathVisit visit) { } try (InputStream in = Files.newInputStream(visit.getPath())) { IndexReader reader = new IndexReader(in); - if (reader.getIndexVersion() < REQUIRED_INDEX_VERSION) { - log.warnf( - "Re-indexing %s - at least Jandex 2.1 must be used to index an application dependency", - visit.getPath()); - return null; - } try { + if (reader.getIndexVersion() < REQUIRED_INDEX_VERSION) { + log.warnf( + "Re-indexing %s - at least Jandex 2.1 must be used to index an application dependency", + visit.getPath()); + return null; + } return reader.read(); } catch (UnsupportedVersion e) { throw new UnsupportedVersion( diff --git a/core/deployment/src/main/java/io/quarkus/deployment/jbang/JBangAugmentorImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/jbang/JBangAugmentorImpl.java index 01eeaca5051ee..946699643f592 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/jbang/JBangAugmentorImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/jbang/JBangAugmentorImpl.java @@ -54,6 +54,9 @@ public void accept(CuratedApplication curatedApplication, Map re if (quarkusBootstrap.getBaseName() != null) { builder.setBaseName(quarkusBootstrap.getBaseName()); } + if (quarkusBootstrap.getOriginalBaseName() != null) { + builder.setOriginalBaseName(quarkusBootstrap.getOriginalBaseName()); + } boolean auxiliaryApplication = curatedApplication.getQuarkusBootstrap().isAuxiliaryApplication(); builder.setAuxiliaryApplication(auxiliaryApplication); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java index 3fa8da77c6a2b..ccec015fef4c1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java @@ -12,12 +12,28 @@ public final class BuildSystemTargetBuildItem extends SimpleBuildItem { private final Path outputDirectory; private final String baseName; + private final String originalBaseName; private final boolean rebuild; private final Properties buildSystemProps; + /** + * @deprecated in favor of {@link #BuildSystemTargetBuildItem(Path, String, String, boolean, Properties)} + * + * @param outputDirectory build output directory + * @param baseName base runner name + * @param rebuild indicates whether the application is being re-built + * @param buildSystemProps build system properties + */ + @Deprecated(forRemoval = true) public BuildSystemTargetBuildItem(Path outputDirectory, String baseName, boolean rebuild, Properties buildSystemProps) { + this(outputDirectory, baseName, baseName, rebuild, buildSystemProps); + } + + public BuildSystemTargetBuildItem(Path outputDirectory, String baseName, String originalBaseName, boolean rebuild, + Properties buildSystemProps) { this.outputDirectory = outputDirectory; this.baseName = baseName; + this.originalBaseName = originalBaseName; this.rebuild = rebuild; this.buildSystemProps = buildSystemProps; } @@ -30,6 +46,10 @@ public String getBaseName() { return baseName; } + public String getOriginalBaseName() { + return originalBaseName; + } + public boolean isRebuild() { return rebuild; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/OutputTargetBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/OutputTargetBuildItem.java index 24479c85b677c..6eb601ace4e1d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/OutputTargetBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/OutputTargetBuildItem.java @@ -17,14 +17,32 @@ public final class OutputTargetBuildItem extends SimpleBuildItem { private final Path outputDirectory; private final String baseName; + private final String originalBaseName; private final boolean rebuild; private final Properties buildSystemProperties; private final Optional> includedOptionalDependencies; + /** + * @deprecated in favor of {@link #OutputTargetBuildItem(Path, String, String, boolean, Properties, Optional)} + * + * @param outputDirectory build output directory + * @param baseName base runner name + * @param rebuild indicates whether the application is being re-built + * @param buildSystemProperties build system properties + * @param includedOptionalDependencies included optional dependencies + */ + @Deprecated(forRemoval = true) public OutputTargetBuildItem(Path outputDirectory, String baseName, boolean rebuild, Properties buildSystemProperties, Optional> includedOptionalDependencies) { + this(outputDirectory, baseName, baseName, rebuild, buildSystemProperties, includedOptionalDependencies); + } + + public OutputTargetBuildItem(Path outputDirectory, String baseName, String originalBaseName, boolean rebuild, + Properties buildSystemProperties, + Optional> includedOptionalDependencies) { this.outputDirectory = outputDirectory; this.baseName = baseName; + this.originalBaseName = originalBaseName; this.rebuild = rebuild; this.buildSystemProperties = buildSystemProperties; this.includedOptionalDependencies = includedOptionalDependencies; @@ -34,10 +52,25 @@ public Path getOutputDirectory() { return outputDirectory; } + /** + * Base name for the Quarkus application runner file. + * + * @return base name for the Quarkus application runner file + */ public String getBaseName() { return baseName; } + /** + * The base name (not including the extension) of the original JAR generated by the build system. + * This name could be different from the value of {@ #getBaseName()}, which will be used for the Quarkus runner file. + * + * @return name of the original JAR generated by the build system + */ + public String getOriginalBaseName() { + return originalBaseName; + } + public boolean isRebuild() { return rebuild; } 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 3e092d838d8fa..dd28e38cf88ac 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 @@ -158,12 +158,13 @@ OutputTargetBuildItem outputTarget(BuildSystemTargetBuildItem bst, PackageConfig Optional> includedOptionalDependencies; if (packageConfig.filterOptionalDependencies) { includedOptionalDependencies = Optional.of(packageConfig.includedOptionalDependencies - .map(set -> set.stream().map(s -> (ArtifactKey) GACT.fromString(s)).collect(Collectors.toSet())) + .map(set -> set.stream().map(s -> ArtifactKey.fromString(s)).collect(Collectors.toSet())) .orElse(Collections.emptySet())); } else { includedOptionalDependencies = Optional.empty(); } - return new OutputTargetBuildItem(path, name, bst.isRebuild(), bst.getBuildSystemProps(), includedOptionalDependencies); + return new OutputTargetBuildItem(path, name, bst.getOriginalBaseName(), bst.isRebuild(), bst.getBuildSystemProps(), + includedOptionalDependencies); } @BuildStep(onlyIf = JarRequired.class) @@ -283,7 +284,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem, //for uberjars we move the original jar, so there is only a single jar in the output directory final Path standardJar = outputTargetBuildItem.getOutputDirectory() - .resolve(outputTargetBuildItem.getBaseName() + ".jar"); + .resolve(outputTargetBuildItem.getOriginalBaseName() + ".jar"); final Path originalJar = Files.exists(standardJar) ? standardJar : null; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java index 1be0521d44027..58c97c1eb237c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -87,7 +87,7 @@ private static String fetchDockerEndpoint() { OutputFilter outputFilter = new OutputFilter(); if (!ExecUtil.execWithTimeout(new File("."), outputFilter, Duration.ofMillis(3000), "docker", "context", "ls", "--format", - "'{{- if .Current -}} {{- .DockerEndpoint -}} {{- end -}}'")) { + "{{- if .Current -}} {{- .DockerEndpoint -}} {{- end -}}")) { LOGGER.debug("Docker context lookup didn't succeed in time"); return null; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java index f9ed878359e32..d616d35e424b6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java @@ -36,8 +36,8 @@ public class LocaleProcessor { @BuildStep(onlyIf = { NativeBuild.class, NonDefaultLocale.class }) void nativeResources(BuildProducer resources) { - resources.produce(new NativeImageResourceBundleBuildItem("sun.util.resources.LocaleNames")); - resources.produce(new NativeImageResourceBundleBuildItem("sun.util.resources.CurrencyNames")); + resources.produce(new NativeImageResourceBundleBuildItem("sun.util.resources.LocaleNames", "java.base")); + resources.produce(new NativeImageResourceBundleBuildItem("sun.util.resources.CurrencyNames", "java.base")); //Adding sun.util.resources.TimeZoneNames is not necessary. } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java index 01786ec5bca51..7a4c59c54b82d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java @@ -51,7 +51,7 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.gizmo.TryBlock; -import io.quarkus.runtime.ReflectionUtil; +import io.quarkus.runtime.NativeImageFeatureUtils; import io.quarkus.runtime.ResourceHelper; import io.quarkus.runtime.graal.ResourcesFeature; import io.quarkus.runtime.graal.WeakReflection; @@ -64,15 +64,15 @@ public class NativeImageFeatureStep { private static final MethodDescriptor IMAGE_SINGLETONS_LOOKUP = ofMethod(ImageSingletons.class, "lookup", Object.class, Class.class); - private static final MethodDescriptor BUILD_TIME_INITIALIZATION = ofMethod( - "org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport", - "initializeAtBuildTime", void.class, String.class, String.class); + private static final MethodDescriptor BUILD_TIME_INITIALIZATION = ofMethod(RuntimeClassInitialization.class, + "initializeAtBuildTime", void.class, String[].class); private static final MethodDescriptor INITIALIZE_CLASSES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class, "initializeAtRunTime", void.class, Class[].class); private static final MethodDescriptor INITIALIZE_PACKAGES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class, "initializeAtRunTime", void.class, String[].class); + public static final String RUNTIME_CLASS_INITIALIZATION_SUPPORT = "org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport"; private static final MethodDescriptor RERUN_INITIALIZATION = ofMethod( - "org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport", + RUNTIME_CLASS_INITIALIZATION_SUPPORT, "rerunInitialization", void.class, Class.class, String.class); public static final String CONFIGURATION_CONDITION = "org.graalvm.nativeimage.impl.ConfigurationCondition"; @@ -87,10 +87,12 @@ public class NativeImageFeatureStep { String.class); private static final MethodDescriptor LOOKUP_METHOD = ofMethod( - ReflectionUtil.class, + NativeImageFeatureUtils.class, "lookupMethod", Method.class, Class.class, String.class, Class[].class); - private static final MethodDescriptor FOR_NAME = ofMethod( - Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class); + + private static final MethodDescriptor FIND_MODULE_METHOD = ofMethod( + NativeImageFeatureUtils.class, + "findModule", Module.class, String.class); private static final MethodDescriptor INVOKE = ofMethod( Method.class, "invoke", Object.class, Object.class, Object[].class); static final String RUNTIME_REFLECTION = RuntimeReflection.class.getName(); @@ -104,6 +106,7 @@ public class NativeImageFeatureStep { public static final MethodDescriptor WEAK_REFLECTION_REGISTRATION = MethodDescriptor.ofMethod(WeakReflection.class, "register", void.class, Feature.BeforeAnalysisAccess.class, Class.class, boolean.class, boolean.class, boolean.class); + public static final String RUNTIME_SERIALIZATION = "org.graalvm.nativeimage.hosted.RuntimeSerialization"; @BuildStep GeneratedResourceBuildItem generateNativeResourcesList(List resources, @@ -169,20 +172,43 @@ public void write(String s, byte[] bytes) { MethodCreator duringSetup = file.getMethodCreator("duringSetup", "V", DURING_SETUP_ACCESS); // Register Lambda Capturing Types if (!lambdaCapturingTypeBuildItems.isEmpty()) { - ResultHandle runtimeSerializationSupportSingleton = duringSetup.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, - duringSetup.loadClassFromTCCL("org.graalvm.nativeimage.impl.RuntimeSerializationSupport")); - ResultHandle configAlwaysTrue = duringSetup.invokeStaticMethod(CONFIGURATION_ALWAYS_TRUE); - for (LambdaCapturingTypeBuildItem i : lambdaCapturingTypeBuildItems) { - TryBlock tryBlock = duringSetup.tryBlock(); + BranchResult graalVm22_3Test = duringSetup + .ifGreaterEqualZero(duringSetup.invokeVirtualMethod(VERSION_COMPARE_TO, + duringSetup.invokeStaticMethod(VERSION_CURRENT), + duringSetup.marshalAsArray(int.class, duringSetup.load(22), duringSetup.load(3)))); + /* GraalVM >= 22.3 */ + try (BytecodeCreator greaterThan22_2 = graalVm22_3Test.trueBranch()) { + MethodDescriptor registerLambdaCapturingClass = ofMethod(RUNTIME_SERIALIZATION, "registerLambdaCapturingClass", + void.class, Class.class); + for (LambdaCapturingTypeBuildItem i : lambdaCapturingTypeBuildItems) { + TryBlock tryBlock = greaterThan22_2.tryBlock(); - tryBlock.invokeInterfaceMethod(REGISTER_LAMBDA_CAPTURING_CLASS, runtimeSerializationSupportSingleton, - configAlwaysTrue, - tryBlock.load(i.getClassName())); + tryBlock.invokeStaticMethod(registerLambdaCapturingClass, + tryBlock.loadClassFromTCCL(i.getClassName())); - CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); - catchBlock.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), - catchBlock.getCaughtException()); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), + catchBlock.getCaughtException()); + } + } + /* GraalVM < 22.3 */ + try (BytecodeCreator smallerThan22_3 = graalVm22_3Test.falseBranch()) { + ResultHandle runtimeSerializationSupportSingleton = smallerThan22_3.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, + smallerThan22_3.loadClassFromTCCL("org.graalvm.nativeimage.impl.RuntimeSerializationSupport")); + ResultHandle configAlwaysTrue = smallerThan22_3.invokeStaticMethod(CONFIGURATION_ALWAYS_TRUE); + + for (LambdaCapturingTypeBuildItem i : lambdaCapturingTypeBuildItems) { + TryBlock tryBlock = smallerThan22_3.tryBlock(); + + tryBlock.invokeInterfaceMethod(REGISTER_LAMBDA_CAPTURING_CLASS, runtimeSerializationSupportSingleton, + configAlwaysTrue, + tryBlock.load(i.getClassName())); + + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), + catchBlock.getCaughtException()); + } } } duringSetup.returnValue(null); @@ -207,12 +233,8 @@ public void write(String s, byte[] bytes) { cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); } - ResultHandle imageSingleton = overallCatch.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, - overallCatch.loadClassFromTCCL("org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport")); - overallCatch.invokeInterfaceMethod(BUILD_TIME_INITIALIZATION, - imageSingleton, - overallCatch.load(""), // empty string means everything - overallCatch.load("Quarkus build time init default")); + overallCatch.invokeStaticMethod(BUILD_TIME_INITIALIZATION, + overallCatch.marshalAsArray(String.class, overallCatch.load(""))); // empty string means initialize everything if (!runtimeInitializedClassBuildItems.isEmpty()) { ResultHandle thisClass = overallCatch.loadClassFromTCCL(GRAAL_FEATURE); @@ -251,6 +273,8 @@ public void write(String s, byte[] bytes) { ResultHandle cl = overallCatch.invokeVirtualMethod(ofMethod(Class.class, "getClassLoader", ClassLoader.class), thisClass); ResultHandle quarkus = overallCatch.load("Quarkus"); + ResultHandle imageSingleton = overallCatch.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, + overallCatch.loadClassFromTCCL(RUNTIME_CLASS_INITIALIZATION_SUPPORT)); for (RuntimeReinitializedClassBuildItem runtimeReinitializedClass : runtimeReinitializedClassBuildItems) { TryBlock tc = overallCatch.tryBlock(); ResultHandle clazz = tc.invokeStaticMethod( @@ -268,6 +292,10 @@ public void write(String s, byte[] bytes) { exports.produce(new JPMSExportBuildItem("org.graalvm.nativeimage.builder", "com.oracle.svm.core.jdk.proxy", GraalVM.Version.VERSION_22_1_0, GraalVM.Version.VERSION_22_3_0)); + ResultHandle versionCompareto22_3Result = overallCatch.invokeVirtualMethod(VERSION_COMPARE_TO, + overallCatch.invokeStaticMethod(VERSION_CURRENT), + overallCatch.marshalAsArray(int.class, overallCatch.load(22), overallCatch.load(3))); + for (NativeImageProxyDefinitionBuildItem proxy : proxies) { ResultHandle array = overallCatch.newArray(Class.class, overallCatch.load(proxy.getClasses().size())); int i = 0; @@ -278,10 +306,7 @@ public void write(String s, byte[] bytes) { } - BranchResult graalVm22_3Test = overallCatch - .ifGreaterEqualZero(overallCatch.invokeVirtualMethod(VERSION_COMPARE_TO, - overallCatch.invokeStaticMethod(VERSION_CURRENT), - overallCatch.marshalAsArray(int.class, overallCatch.load(22), overallCatch.load(3)))); + BranchResult graalVm22_3Test = overallCatch.ifGreaterEqualZero(versionCompareto22_3Result); /* GraalVM >= 22.3 */ try (BytecodeCreator greaterThan22_2 = graalVm22_3Test.trueBranch()) { MethodDescriptor registerMethod = ofMethod("org.graalvm.nativeimage.hosted.RuntimeProxyCreation", @@ -385,26 +410,22 @@ public void write(String s, byte[] bytes) { /* GraalVM >= 22.3 */ try (BytecodeCreator greaterThan22_2 = graalVm22_3Test.trueBranch()) { - ResultHandle runtimeResourceSupportClass = greaterThan22_2.loadClassFromTCCL(RUNTIME_RESOURCE_SUPPORT); - ResultHandle addResourceBundlesParams = greaterThan22_2.marshalAsArray(Class.class, - greaterThan22_2.loadClassFromTCCL(CONFIGURATION_CONDITION), - greaterThan22_2.loadClassFromTCCL(String.class)); - ResultHandle addResourceBundlesMethod = greaterThan22_2.invokeStaticMethod( - LOOKUP_METHOD, - runtimeResourceSupportClass, greaterThan22_2.load("addResourceBundles"), addResourceBundlesParams); - ResultHandle runtimeResourceSupport = greaterThan22_2.invokeStaticMethod( - IMAGE_SINGLETONS_LOOKUP, - runtimeResourceSupportClass); - ResultHandle configAlwaysTrue = greaterThan22_2.invokeStaticMethod(CONFIGURATION_ALWAYS_TRUE); + MethodDescriptor addResourceBundle = ofMethod("org.graalvm.nativeimage.hosted.RuntimeResourceAccess", + "addResourceBundle", void.class, Module.class, String.class); for (NativeImageResourceBundleBuildItem i : resourceBundles) { - TryBlock et = greaterThan22_2.tryBlock(); - - et.invokeVirtualMethod( - INVOKE, - addResourceBundlesMethod, runtimeResourceSupport, - et.marshalAsArray(Object.class, configAlwaysTrue, et.load(i.getBundleName()))); - CatchBlockCreator c = et.addCatch(Throwable.class); + TryBlock tc = greaterThan22_2.tryBlock(); + + String moduleName = i.getModuleName(); + ResultHandle moduleNameHandle; + if (moduleName == null) { + moduleNameHandle = tc.loadNull(); + } else { + moduleNameHandle = tc.load(moduleName); + } + ResultHandle module = tc.invokeStaticMethod(FIND_MODULE_METHOD, moduleNameHandle); + tc.invokeStaticMethod(addResourceBundle, module, tc.load(i.getBundleName())); + CatchBlockCreator c = tc.addCatch(Throwable.class); //c.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), c.getCaughtException()); } } @@ -468,14 +489,7 @@ public void write(String s, byte[] bytes) { TryBlock tc = mv.tryBlock(); - ResultHandle currentThread = tc - .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); - ResultHandle tccl = tc.invokeVirtualMethod( - ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), - currentThread); - ResultHandle clazz = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load(entry.getKey()), tc.load(false), tccl); + ResultHandle clazz = tc.loadClassFromTCCL(entry.getKey()); //we call these methods first, so if they are going to throw an exception it happens before anything has been registered ResultHandle constructors = tc .invokeVirtualMethod(ofMethod(Class.class, "getDeclaredConstructors", Constructor[].class), clazz); @@ -575,14 +589,7 @@ public void write(String s, byte[] bytes) { TryBlock tc = mv.tryBlock(); - ResultHandle currentThread = tc - .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); - ResultHandle tccl = tc.invokeVirtualMethod( - ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), - currentThread); - ResultHandle clazz = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load(className), tc.load(false), tccl); + ResultHandle clazz = tc.loadClassFromTCCL(className); //we call these methods first, so if they are going to throw an exception it happens before anything has been registered ResultHandle constructors = tc .invokeVirtualMethod(ofMethod(Class.class, "getDeclaredConstructors", Constructor[].class), clazz); @@ -666,15 +673,7 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator TryBlock tc = addSerializationForClass.tryBlock(); - ResultHandle currentThread = tc - .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); - ResultHandle tccl = tc.invokeVirtualMethod( - ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), - currentThread); - - ResultHandle runtimeSerializationClass = tc.invokeStaticMethod(FOR_NAME, - tc.load("org.graalvm.nativeimage.hosted.RuntimeSerialization"), - tc.load(false), tccl); + ResultHandle runtimeSerializationClass = tc.loadClassFromTCCL(RUNTIME_SERIALIZATION); ResultHandle registerArgTypes = tc.newArray(Class.class, tc.load(1)); tc.writeArrayValue(registerArgTypes, 0, tc.loadClassFromTCCL(Class[].class)); ResultHandle registerLookupMethod = tc.invokeStaticMethod(LOOKUP_METHOD, runtimeSerializationClass, diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java index f2cfba72a2ce0..5f959fad51bac 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java @@ -12,6 +12,6 @@ public NativeImageResourceBundleBuildItem nativeImageResourceBundle() { * This might no longer be required if GraalVM auto-includes it in a future release. * See https://github.com/oracle/graal/issues/2005 for more details about it. */ - return new NativeImageResourceBundleBuildItem("sun.security.util.Resources"); + return new NativeImageResourceBundleBuildItem("sun.security.util.Resources", "java.base"); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/CommandLineUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/CommandLineUtil.java new file mode 100644 index 0000000000000..b9fdc7d7ffe59 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/CommandLineUtil.java @@ -0,0 +1,107 @@ +package io.quarkus.deployment.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * This class contains code coming from org.apache.maven.shared.utils.cli.CommandLineUtils. + *

+ * We don't want to directly use code coming from Maven as this artifact should be Maven-agnostic. + * + * @author Trygve Laugstøl + */ +public final class CommandLineUtil { + + private CommandLineUtil() { + } + + /** + * @param toProcess The command line to translate. + * @return The array of translated parts. + * @throws IllegalStateException in case of unbalanced quotes. + */ + public static String[] translateCommandline(String toProcess) { + if ((toProcess == null) || (toProcess.length() == 0)) { + return new String[0]; + } + + // parse with a simple finite state machine + + final int normal = 0; + final int inQuote = 1; + final int inDoubleQuote = 2; + boolean inEscape = false; + int state = normal; + final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' \\", true); + List tokens = new ArrayList(); + StringBuilder current = new StringBuilder(); + + while (tok.hasMoreTokens()) { + String nextTok = tok.nextToken(); + switch (state) { + case inQuote: + if ("\'".equals(nextTok)) { + if (inEscape) { + current.append(nextTok); + inEscape = false; + } else { + state = normal; + } + } else { + current.append(nextTok); + inEscape = "\\".equals(nextTok); + } + break; + case inDoubleQuote: + if ("\"".equals(nextTok)) { + if (inEscape) { + current.append(nextTok); + inEscape = false; + } else { + state = normal; + } + } else { + current.append(nextTok); + inEscape = "\\".equals(nextTok); + } + break; + default: + if ("\'".equals(nextTok)) { + if (inEscape) { + inEscape = false; + current.append(nextTok); + } else { + state = inQuote; + } + } else if ("\"".equals(nextTok)) { + if (inEscape) { + inEscape = false; + current.append(nextTok); + } else { + state = inDoubleQuote; + } + } else if (" ".equals(nextTok)) { + if (current.length() != 0) { + tokens.add(current.toString()); + current.setLength(0); + } + } else { + current.append(nextTok); + inEscape = "\\".equals(nextTok); + } + break; + } + } + + if (current.length() != 0) { + tokens.add(current.toString()); + } + + if ((state == inQuote) || (state == inDoubleQuote)) { + throw new IllegalStateException("unbalanced quotes in " + toProcess); + } + + return tokens.toArray(new String[tokens.size()]); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java index 1666ef4e6264f..4d2d7e01e923d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java @@ -11,6 +11,7 @@ import java.util.Set; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassType; @@ -29,6 +30,7 @@ public final class JandexUtil { public final static DotName DOTNAME_OBJECT = DotName.createSimple(Object.class.getName()); + public final static DotName DOTNAME_RECORD = DotName.createSimple("java.lang.Record"); private JandexUtil() { } @@ -119,8 +121,8 @@ private static List findParametersRecursively(Type type, DotName target, return null; } - // always end at Object - if (DOTNAME_OBJECT.equals(name)) { + // always end at Object or Record + if (DOTNAME_OBJECT.equals(name) || DOTNAME_RECORD.equals(name)) { return null; } @@ -288,25 +290,32 @@ private static ClassInfo fetchFromIndex(DotName dotName, IndexView index) { } /** - * Returns the enclosing class of the given annotation instance. For field or method annotations this - * will return the enclosing class. For parameters, this will return the enclosing class of the enclosing - * method. For classes, it will return the class itself. + * Returns the enclosing class of the given annotation instance. For field, method or record component annotations, + * this will return the enclosing class. For parameters, this will return the enclosing class of the enclosing + * method. For classes, it will return the class itself. For type annotations, it will return the class enclosing + * the annotated type usage. * - * @param annotationInstance the annotation whose enclosing class to look up. - * @return the enclosing class. + * @param annotationInstance the annotation whose enclosing class to look up + * @return the enclosing class */ public static ClassInfo getEnclosingClass(AnnotationInstance annotationInstance) { - switch (annotationInstance.target().kind()) { + return getEnclosingClass(annotationInstance.target()); + } + + private static ClassInfo getEnclosingClass(AnnotationTarget annotationTarget) { + switch (annotationTarget.kind()) { case FIELD: - return annotationInstance.target().asField().declaringClass(); + return annotationTarget.asField().declaringClass(); case METHOD: - return annotationInstance.target().asMethod().declaringClass(); + return annotationTarget.asMethod().declaringClass(); case METHOD_PARAMETER: - return annotationInstance.target().asMethodParameter().method().declaringClass(); + return annotationTarget.asMethodParameter().method().declaringClass(); + case RECORD_COMPONENT: + return annotationTarget.asRecordComponent().declaringClass(); case CLASS: - return annotationInstance.target().asClass(); + return annotationTarget.asClass(); case TYPE: - return annotationInstance.target().asType().asClass(); // TODO is it legal here or should I throw ? + return getEnclosingClass(annotationTarget.asType().enclosingTarget()); default: throw new RuntimeException(); // this should not occur } @@ -323,7 +332,7 @@ public static ClassInfo getEnclosingClass(AnnotationInstance annotationInstance) * @throws BuildException if one of the superclasses is not indexed. */ public static boolean isSubclassOf(IndexView index, ClassInfo info, DotName parentName) throws BuildException { - if (info.superName().equals(DOTNAME_OBJECT)) { + if (info.superName().equals(DOTNAME_OBJECT) || info.superName().equals(DOTNAME_RECORD)) { return false; } if (info.superName().equals(parentName)) { diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java index 6d9cca4a075da..a2007cf6732db 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java @@ -291,6 +291,9 @@ private BuildResult runAugment(boolean firstRun, Set changedResources, if (quarkusBootstrap.getBaseName() != null) { builder.setBaseName(quarkusBootstrap.getBaseName()); } + if (quarkusBootstrap.getOriginalBaseName() != null) { + builder.setOriginalBaseName(quarkusBootstrap.getOriginalBaseName()); + } boolean auxiliaryApplication = curatedApplication.getQuarkusBootstrap().isAuxiliaryApplication(); builder.setAuxiliaryApplication(auxiliaryApplication); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ReflectionUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/NativeImageFeatureUtils.java similarity index 55% rename from core/runtime/src/main/java/io/quarkus/runtime/ReflectionUtil.java rename to core/runtime/src/main/java/io/quarkus/runtime/NativeImageFeatureUtils.java index face04fad7ed8..cdecbd71dcedf 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ReflectionUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/NativeImageFeatureUtils.java @@ -2,7 +2,7 @@ import java.lang.reflect.Method; -public class ReflectionUtil { +public class NativeImageFeatureUtils { public static Method lookupMethod(Class declaringClass, String methodName, Class... parameterTypes) throws NoSuchMethodException { @@ -10,4 +10,11 @@ public static Method lookupMethod(Class declaringClass, String methodName, Cl result.setAccessible(true); return result; } + + public static Module findModule(String moduleName) { + if (moduleName == null) { + return ClassLoader.getSystemClassLoader().getUnnamedModule(); + } + return ModuleLayer.boot().findModule(moduleName).orElseThrow(); + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocMapKey.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocMapKey.java index a6d1096c26c85..74e6b54fd62a6 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocMapKey.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocMapKey.java @@ -1,6 +1,7 @@ package io.quarkus.runtime.annotations; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -13,7 +14,7 @@ */ @Documented @Retention(SOURCE) -@Target({ FIELD, PARAMETER }) +@Target({ FIELD, PARAMETER, METHOD }) public @interface ConfigDocMapKey { String value(); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocSection.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocSection.java index 86915c752b300..372cc7782d621 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocSection.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigDocSection.java @@ -1,6 +1,7 @@ package io.quarkus.runtime.annotations; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -14,6 +15,6 @@ */ @Documented @Retention(SOURCE) -@Target({ FIELD, PARAMETER }) +@Target({ FIELD, PARAMETER, METHOD }) public @interface ConfigDocSection { } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index a0e3d24df1d41..d971720ba9488 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -5,6 +5,8 @@ import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE_PARENT; import static io.smallrye.config.SmallRyeConfigBuilder.META_INF_MICROPROFILE_CONFIG_PROPERTIES; +import static java.lang.Integer.MAX_VALUE; +import static java.lang.Integer.MIN_VALUE; import java.io.IOException; import java.net.URL; @@ -13,7 +15,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -39,13 +40,13 @@ import io.smallrye.config.EnvConfigSource; import io.smallrye.config.FallbackConfigSourceInterceptor; import io.smallrye.config.KeyMap; -import io.smallrye.config.KeyMapBackedConfigSource; import io.smallrye.config.Priorities; import io.smallrye.config.ProfileConfigSourceInterceptor; import io.smallrye.config.RelocateConfigSourceInterceptor; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; import io.smallrye.config.SysPropConfigSource; +import io.smallrye.config.common.MapBackedConfigSource; import io.smallrye.config.common.utils.ConfigSourceUtil; /** @@ -115,10 +116,8 @@ public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final b builder.addDiscoveredValidator(); builder.withDefaultValue(UUID_KEY, UUID.randomUUID().toString()); builder.withSources(new DotEnvConfigSourceProvider()); - builder.withSources( - new DefaultsConfigSource(loadBuildTimeRunTimeValues(), "BuildTime RunTime Fixed", Integer.MAX_VALUE)); - builder.withSources( - new DefaultsConfigSource(loadRunTimeDefaultValues(), "RunTime Defaults", Integer.MIN_VALUE + 100)); + builder.withSources(new DefaultsConfigSource(loadBuildTimeRunTimeValues(), "BuildTime RunTime Fixed", MAX_VALUE)); + builder.withSources(new DefaultsConfigSource(loadRunTimeDefaultValues(), "RunTime Defaults", MIN_VALUE + 100)); } else { List sources = new ArrayList<>(); sources.addAll(classPathSources(META_INF_MICROPROFILE_CONFIG_PROPERTIES, classLoader)); @@ -381,43 +380,35 @@ public Set getPropertyNames() { } } - static class DefaultsConfigSource extends KeyMapBackedConfigSource { + static class DefaultsConfigSource extends MapBackedConfigSource { private final KeyMap wildcards; - private final Set propertyNames; public DefaultsConfigSource(final Map properties, final String name, final int ordinal) { - super(name, ordinal, propertiesToKeyMap(properties)); + // Defaults may contain wildcards, but we don't want to expose them in getPropertyNames, so we need to filter them + super(name, filterWildcards(properties), ordinal); this.wildcards = new KeyMap<>(); - this.propertyNames = new HashSet<>(); for (Map.Entry entry : properties.entrySet()) { if (entry.getKey().contains("*")) { this.wildcards.findOrAdd(entry.getKey()).putRootValue(entry.getValue()); - } else { - this.propertyNames.add(entry.getKey()); } } } - @Override - public Set getPropertyNames() { - return propertyNames; - } - @Override public String getValue(final String propertyName) { String value = super.getValue(propertyName); return value == null ? wildcards.findRootValue(propertyName) : value; } - private static KeyMap propertiesToKeyMap(final Map properties) { - KeyMap keyMap = new KeyMap<>(); + private static Map filterWildcards(final Map properties) { + Map filtered = new HashMap<>(); for (Map.Entry entry : properties.entrySet()) { if (entry.getKey().contains("*")) { continue; } - keyMap.findOrAdd(entry.getKey()).putRootValue(entry.getValue()); + filtered.put(entry.getKey(), entry.getValue()); } - return keyMap; + return filtered; } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java index d8bcb2b29ab90..2c801a999901a 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java @@ -1,6 +1,7 @@ package io.quarkus.cli.build; import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; @@ -32,6 +33,7 @@ import picocli.CommandLine; public class MavenRunner implements BuildSystemRunner { + public static String MAVEN_SETTINGS = "maven.settings"; static final String[] windowsWrapper = { "mvnw.cmd", "mvnw.bat" }; static final String otherWrapper = "mvnw"; @@ -238,13 +240,13 @@ void setMavenProperties(ArrayDeque args, boolean batchMode) { args.addFirst("-Dstyle.color=always"); } - String mavenSettings = propertiesOptions.properties.remove("maven.settings"); + String mavenSettings = propertiesOptions.properties.remove(MAVEN_SETTINGS); if (mavenSettings != null && !mavenSettings.isEmpty()) { args.add("-s"); args.add(mavenSettings); } else { - mavenSettings = System.getProperty("maven.settings"); - if (mavenSettings != null && !mavenSettings.isEmpty()) { + mavenSettings = System.getProperty(MAVEN_SETTINGS); + if (mavenSettings != null && !mavenSettings.isEmpty() && Files.exists(Path.of(mavenSettings))) { args.add("-s"); args.add(mavenSettings); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java index ddf060402c37e..2ee05931bef2b 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java @@ -1,5 +1,8 @@ package io.quarkus.cli; +import static io.quarkus.cli.build.MavenRunner.MAVEN_SETTINGS; +import static org.apache.maven.cli.MavenCli.LOCAL_REPO_PROPERTY; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; @@ -12,6 +15,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; import org.junit.jupiter.api.Assertions; @@ -20,6 +26,9 @@ public class CliDriver { static final PrintStream stdout = System.out; static final PrintStream stderr = System.err; + private static final BinaryOperator ARG_FORMATTER = (key, value) -> "-D" + key + "=" + value; + private static final UnaryOperator REPO_ARG_FORMATTER = value -> ARG_FORMATTER.apply(LOCAL_REPO_PROPERTY, value); + private static final UnaryOperator SETTINGS_ARG_FORMATTER = value -> ARG_FORMATTER.apply(MAVEN_SETTINGS, value); public static class CliDriverBuilder { @@ -63,8 +72,10 @@ public Result execute() throws Exception { newArgs.subList(index, newArgs.size()).clear(); } - propagateProperty("maven.repo.local", mavenLocalRepo, newArgs); - propagateProperty("maven.settings", mavenSettings, newArgs); + Optional.ofNullable(mavenLocalRepo).or(CliDriver::getMavenLocalRepoProperty).map(REPO_ARG_FORMATTER) + .ifPresent(newArgs::add); + Optional.ofNullable(mavenSettings).or(CliDriver::getMavenSettingsProperty).map(SETTINGS_ARG_FORMATTER) + .ifPresent(newArgs::add); newArgs.add("--cli-test"); newArgs.add("--cli-test-dir"); @@ -81,7 +92,7 @@ public Result execute() throws Exception { PrintStream errPs = new PrintStream(err); System.setErr(errPs); - final Map originalProps = collectOverridenProps(newArgs); + final Map originalProps = collectOverriddenProps(newArgs); Result result = new Result(); QuarkusCli cli = new QuarkusCli(); @@ -109,7 +120,7 @@ protected void resetProperties(Map originalProps) { } } - protected Map collectOverridenProps(List newArgs) { + protected Map collectOverriddenProps(List newArgs) { final Map originalProps = new HashMap<>(); for (String s : newArgs) { if (s.startsWith("-D")) { @@ -121,22 +132,14 @@ protected Map collectOverridenProps(List newArgs) { originalProps.put(propName, origValue); } else if (System.getProperties().contains(propName)) { originalProps.put(propName, "true"); + } else { + originalProps.put(propName, null); } } } } return originalProps; } - - private static void propagateProperty(String propName, String testValue, List args) { - if (testValue == null) { - testValue = System.getProperty(propName); - if (testValue == null) { - return; - } - } - args.add("-D" + propName + "=" + testValue); - } } public static CliDriverBuilder builder() { @@ -144,14 +147,8 @@ public static CliDriverBuilder builder() { } public static void preserveLocalRepoSettings(Collection args) { - String s = convertToProperty("maven.repo.local"); - if (s != null) { - args.add(s); - } - s = convertToProperty("maven.settings"); - if (s != null) { - args.add(s); - } + getMavenLocalRepoProperty().map(REPO_ARG_FORMATTER).ifPresent(args::add); + getMavenSettingsProperty().map(SETTINGS_ARG_FORMATTER).ifPresent(args::add); } public static Result executeArbitraryCommand(Path startingDir, String... args) throws Exception { @@ -439,12 +436,12 @@ public static void validateApplicationProperties(Path projectRoot, List "Properties file should contain " + conf + ". Found:\n" + propertiesFile)); } - private static String convertToProperty(String name) { - String value = System.getProperty(name); - if (value != null) { - return "-D" + name + "=" + value; - } - return null; + private static Optional getMavenLocalRepoProperty() { + return Optional.ofNullable(System.getProperty(LOCAL_REPO_PROPERTY)); + } + + private static Optional getMavenSettingsProperty() { + return Optional.ofNullable(System.getProperty(MAVEN_SETTINGS)).filter(value -> Files.exists(Path.of(value))); } private static void retryDelete(File file) { diff --git a/devtools/cli/src/test/java/io/quarkus/cli/RegistryClientBuilderTestBase.java b/devtools/cli/src/test/java/io/quarkus/cli/RegistryClientBuilderTestBase.java index 00a402bcda5ca..d101dbe001899 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/RegistryClientBuilderTestBase.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/RegistryClientBuilderTestBase.java @@ -1,7 +1,7 @@ package io.quarkus.cli; -import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.nio.file.Files; @@ -59,7 +59,7 @@ static void setup() throws Exception { final BootstrapMavenContext mavenContext = new BootstrapMavenContext( BootstrapMavenContext.config().setWorkspaceDiscovery(false)); - final Settings settings = getBaseMavenSettings(mavenContext.getUserSettings().toPath()); + final Settings settings = getBaseMavenSettings(mavenContext.getUserSettings()); Profile profile = new Profile(); settings.addActiveProfile("qs-test-registry"); @@ -106,11 +106,9 @@ protected static String getCurrentQuarkusVersion() { return v; } - private static Settings getBaseMavenSettings(Path mavenSettings) throws IOException { - if (Files.exists(mavenSettings)) { - try (BufferedReader reader = Files.newBufferedReader(mavenSettings)) { - return new DefaultSettingsReader().read(reader, Map.of()); - } + private static Settings getBaseMavenSettings(File mavenSettings) throws IOException { + if (mavenSettings != null && mavenSettings.exists()) { + return new DefaultSettingsReader().read(mavenSettings, Map.of()); } return new Settings(); } diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index d800441c15f2d..5368eedb8580b 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -31,7 +31,7 @@ subprojects { implementation "io.quarkus:quarkus-bootstrap-core:${version}" testImplementation gradleTestKit() - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.assertj:assertj-core:3.23.1' } diff --git a/devtools/gradle/gradle-application-plugin/build.gradle b/devtools/gradle/gradle-application-plugin/build.gradle index 2f24773c33488..c7bba6e4c532f 100644 --- a/devtools/gradle/gradle-application-plugin/build.gradle +++ b/devtools/gradle/gradle-application-plugin/build.gradle @@ -33,3 +33,7 @@ pluginBundle { vcsUrl = 'https://github.com/quarkusio/quarkus' tags = ['quarkus', 'quarkusio', 'graalvm'] } + +test { + systemProperty 'kotlin_version', project.kotlin_version +} \ No newline at end of file diff --git a/devtools/gradle/gradle-application-plugin/pom.xml b/devtools/gradle/gradle-application-plugin/pom.xml index cefcb1fc66b3c..fa6bf4c85e9a5 100644 --- a/devtools/gradle/gradle-application-plugin/pom.xml +++ b/devtools/gradle/gradle-application-plugin/pom.xml @@ -54,6 +54,18 @@ quarkus-devmode-test-utils test + + org.jetbrains.kotlin + kotlin-gradle-plugin + ${kotlin.version} + test + + + org.checkerframework + checker-qual + + + diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 0f29e3f3c2e51..000e71d0745b1 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -1,10 +1,15 @@ package io.quarkus.gradle; import static org.assertj.core.api.Assertions.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -16,12 +21,18 @@ import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskContainer; import org.gradle.testfixtures.ProjectBuilder; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import io.quarkus.gradle.extension.QuarkusPluginExtension; public class QuarkusPluginTest { + @TempDir + Path testProjectDir; + @Test public void shouldCreateTasks() { Project project = ProjectBuilder.builder().build(); @@ -67,7 +78,7 @@ public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { } @Test - public void shouldReturnMutlipleOutputSourceDirectories() { + public void shouldReturnMultipleOutputSourceDirectories() { Project project = ProjectBuilder.builder().build(); project.getPluginManager().apply(QuarkusPlugin.ID); project.getPluginManager().apply("java"); @@ -84,7 +95,68 @@ public void shouldReturnMutlipleOutputSourceDirectories() { } - private static final List getDependantProvidedTaskName(Task task) { + @Test + public void shouldNotFailOnProjectDependenciesWithoutMain() throws IOException { + var kotlinVersion = System.getProperty("kotlin_version", "1.7.10"); + var settingFile = testProjectDir.resolve("settings.gradle.kts"); + var mppProjectDir = testProjectDir.resolve("mpp"); + var quarkusProjectDir = testProjectDir.resolve("quarkus"); + var mppBuild = mppProjectDir.resolve("build.gradle.kts"); + var quarkusBuild = quarkusProjectDir.resolve("build.gradle.kts"); + Files.createDirectory(mppProjectDir); + Files.createDirectory(quarkusProjectDir); + Files.writeString(settingFile, + "rootProject.name = \"quarkus-mpp-sample\"\n" + + "\n" + + "include(\n" + + " \"mpp\",\n" + + " \"quarkus\"\n" + + ")"); + + Files.writeString(mppBuild, + "buildscript {\n" + + " repositories {\n" + + " mavenLocal()\n" + + " mavenCentral()\n" + + " }\n" + + " dependencies {\n" + + " classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin:" + kotlinVersion + "\")\n" + + " }\n" + + "}\n" + + "\n" + + "apply(plugin = \"org.jetbrains.kotlin.multiplatform\")\n" + + "\n" + + "repositories {\n" + + " mavenCentral()\n" + + "}\n" + + "\n" + + "configure{\n" + + " jvm()\n" + + "}"); + + Files.writeString(quarkusBuild, + "plugins {\n" + + " id(\"io.quarkus\")\n" + + "}\n" + + "\n" + + "repositories {\n" + + " mavenCentral()\n" + + "}\n" + + "\n" + + "dependencies {\n" + + " implementation(project(\":mpp\"))\n" + + "}"); + + BuildResult result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir.toFile()) + .withArguments("quarkusGenerateCode") + .build(); + + assertEquals(SUCCESS, result.task(":quarkus:quarkusGenerateCode").getOutcome()); + } + + private static List getDependantProvidedTaskName(Task task) { List dependantTaskNames = new ArrayList<>(); for (Object t : task.getDependsOn()) { try { diff --git a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java index 5508c2e8e1a7f..1c01060da4c65 100644 --- a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java +++ b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/dependency/DeploymentClasspathBuilder.java @@ -43,9 +43,7 @@ public void exportDeploymentClasspath(String configurationName) { dependencies); } else { DependencyUtils.requireDeploymentDependency(deploymentConfigurationName, extension, dependencies); - if (!alreadyProcessed.add(extension.getExtensionId())) { - continue; - } + alreadyProcessed.add(extension.getExtensionId()); } } }); @@ -73,9 +71,9 @@ private Set collectQuarkusExtensions(ResolvedDependency dep } Set extensions = new LinkedHashSet<>(); for (ResolvedArtifact moduleArtifact : dependency.getModuleArtifacts()) { - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(project, moduleArtifact); - if (extension != null) { - extensions.add(extension); + var optionalExtension = DependencyUtils.getOptionalExtensionInfo(project, moduleArtifact); + if (optionalExtension.isPresent()) { + extensions.add(optionalExtension.get()); return extensions; } } diff --git a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java index be96802daf78d..f2a1bb28cac0a 100644 --- a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java +++ b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/tasks/ValidateExtensionTask.java @@ -2,7 +2,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -18,7 +20,6 @@ import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.extension.gradle.QuarkusExtensionConfiguration; import io.quarkus.gradle.tooling.dependency.DependencyUtils; -import io.quarkus.gradle.tooling.dependency.ExtensionDependency; public class ValidateExtensionTask extends DefaultTask { @@ -62,13 +63,7 @@ public void validateExtension() { deploymentModuleKeys); deploymentModuleKeys.removeAll(existingDeploymentModuleKeys); - boolean hasErrors = false; - if (!invalidRuntimeArtifacts.isEmpty()) { - hasErrors = true; - } - if (!deploymentModuleKeys.isEmpty()) { - hasErrors = true; - } + boolean hasErrors = !invalidRuntimeArtifacts.isEmpty() || !deploymentModuleKeys.isEmpty(); if (hasErrors) { printValidationErrors(invalidRuntimeArtifacts, deploymentModuleKeys); @@ -76,15 +71,13 @@ public void validateExtension() { } private List collectRuntimeExtensionsDeploymentKeys(Set runtimeArtifacts) { - List runtimeExtensions = new ArrayList<>(); - for (ResolvedArtifact resolvedArtifact : runtimeArtifacts) { - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(getProject(), resolvedArtifact); - if (extension != null) { - runtimeExtensions.add(new AppArtifactKey(extension.getDeploymentModule().getGroupId(), - extension.getDeploymentModule().getArtifactId())); - } - } - return runtimeExtensions; + return runtimeArtifacts.stream() + .map(resolvedArtifact -> DependencyUtils.getOptionalExtensionInfo(getProject(), resolvedArtifact)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(extension -> new AppArtifactKey(extension.getDeploymentModule().getGroupId(), + extension.getDeploymentModule().getArtifactId())) + .collect(Collectors.toList()); } private List findExtensionInConfiguration(Set deploymentArtifacts, diff --git a/devtools/gradle/gradle-model/build.gradle b/devtools/gradle/gradle-model/build.gradle index 15e648b26cae7..29e30073f46cf 100644 --- a/devtools/gradle/gradle-model/build.gradle +++ b/devtools/gradle/gradle-model/build.gradle @@ -3,6 +3,7 @@ dependencies { implementation "io.quarkus:quarkus-bootstrap-gradle-resolver:${version}" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:${kotlin_version}" testImplementation "io.quarkus:quarkus-devtools-testing:${version}" + testImplementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" } task sourcesJar(type: Jar, dependsOn: classes) { diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index 6ab5650d7b5a9..7cd6180ccbfd6 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -6,7 +6,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -37,8 +40,7 @@ public class ConditionalDependenciesEnabler { private final Set existingArtifacts = new HashSet<>(); private final List unsatisfiedConditionalDeps = new ArrayList<>(); - public ConditionalDependenciesEnabler(Project project, LaunchMode mode, - Configuration platforms) { + public ConditionalDependenciesEnabler(Project project, LaunchMode mode, Configuration platforms) { this.project = project; this.enforcedPlatforms = platforms; @@ -59,7 +61,7 @@ public ConditionalDependenciesEnabler(Project project, LaunchMode mode, final Dependency conditionalDep = unsatisfiedConditionalDeps.get(i); // Try to resolve it with the latest evolved graph available if (resolveConditionalDependency(conditionalDep)) { - // Mark the resolution as a success so we know the graph evolved + // Mark the resolution as a success, so we know the graph has evolved satisfiedConditionalDeps = true; unsatisfiedConditionalDeps.remove(i); } else { @@ -88,23 +90,25 @@ private void reset() { } private void collectConditionalDependencies(Set runtimeArtifacts) { - // For every artifact in the dependency graph: - for (ResolvedArtifact artifact : runtimeArtifacts) { - // Add to master list of artifacts: - existingArtifacts.add(getKey(artifact)); - ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(project, artifact); - // If this artifact represents an extension: - if (extension != null) { - // Add to master list of accepted extensions: - allExtensions.put(extension.getExtensionId(), extension); - for (Dependency conditionalDep : extension.getConditionalDependencies()) { - // If the dependency is not present yet in the graph, queue it for resolution later - if (!exists(conditionalDep)) { - queueConditionalDependency(extension, conditionalDep); - } - } - } - } + addToMasterList(runtimeArtifacts); + var artifactExtensions = getArtifactExtensions(runtimeArtifacts); + allExtensions.putAll(artifactExtensions); + artifactExtensions.forEach((ignored, extension) -> queueAbsentExtensionConditionalDependencies(extension)); + } + + private void addToMasterList(Set artifacts) { + artifacts.stream().map(ConditionalDependenciesEnabler::getKey).forEach(existingArtifacts::add); + } + + private Map getArtifactExtensions(Set runtimeArtifacts) { + return runtimeArtifacts.stream() + .flatMap(artifact -> DependencyUtils.getOptionalExtensionInfo(project, artifact).stream()) + .collect(Collectors.toMap(ExtensionDependency::getExtensionId, Function.identity())); + } + + private void queueAbsentExtensionConditionalDependencies(ExtensionDependency extension) { + extension.getConditionalDependencies().stream().filter(dep -> !exists(dep)) + .forEach(dep -> queueConditionalDependency(extension, dep)); } private boolean resolveConditionalDependency(Dependency conditionalDep) { @@ -112,51 +116,31 @@ private boolean resolveConditionalDependency(Dependency conditionalDep) { final Configuration conditionalDeps = createConditionalDependenciesConfiguration(project, conditionalDep); Set resolvedArtifacts = conditionalDeps.getResolvedConfiguration().getResolvedArtifacts(); - boolean satisfied = false; - // Resolved artifacts don't have great linking back to the original artifact, so I think - // this loop is trying to find the artifact that represents the original conditional - // dependency - for (ResolvedArtifact artifact : resolvedArtifacts) { - if (conditionalDep.getName().equals(artifact.getName()) - && conditionalDep.getVersion().equals(artifact.getModuleVersion().getId().getVersion()) - && artifact.getModuleVersion().getId().getGroup().equals(conditionalDep.getGroup())) { - // Once the dependency is found, reload the extension info from within - final ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); - // Now check if this conditional dependency is resolved given the latest graph evolution - if (extensionDependency != null && (extensionDependency.getDependencyConditions().isEmpty() - || exist(extensionDependency.getDependencyConditions()))) { - satisfied = true; - enableConditionalDependency(extensionDependency.getExtensionId()); - break; - } - } + boolean isConditionalDependencyResolved = resolvedArtifacts.stream() + .filter(artifact -> areEquals(conditionalDep, artifact)) + .flatMap(artifact -> DependencyUtils.getOptionalExtensionInfo(project, artifact).stream()) + .filter(extension -> extension.getDependencyConditions().isEmpty() + || exist(extension.getDependencyConditions())) + .findFirst().map(extension -> { + enableConditionalDependency(extension.getExtensionId()); + return true; + }).orElse(false); + + if (isConditionalDependencyResolved) { + addToMasterList(resolvedArtifacts); + var artifactExtensions = getArtifactExtensions(resolvedArtifacts); + artifactExtensions.forEach((id, extension) -> extension.setConditional(true)); + allExtensions.putAll(artifactExtensions); + artifactExtensions.forEach((ignored, extension) -> queueAbsentExtensionConditionalDependencies(extension)); } - // No resolution (yet); give up. - if (!satisfied) { - return false; - } + return isConditionalDependencyResolved; + } - // The conditional dependency resolved! Let's now add all of /its/ dependencies - for (ResolvedArtifact artifact : resolvedArtifacts) { - // First add the artifact to the master list - existingArtifacts.add(getKey(artifact)); - ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); - if (extensionDependency == null) { - continue; - } - // If this artifact represents an extension, mark this one as a conditional extension - extensionDependency.setConditional(true); - // Add to the master list of accepted extensions - allExtensions.put(extensionDependency.getExtensionId(), extensionDependency); - for (Dependency cd : extensionDependency.getConditionalDependencies()) { - // Add any unsatisfied/unresolved conditional dependencies of this dependency to the queue - if (!exists(cd)) { - queueConditionalDependency(extensionDependency, cd); - } - } - } - return satisfied; + private boolean areEquals(Dependency dependency, ResolvedArtifact artifact) { + return dependency.getName().equals(artifact.getName()) + && Objects.equals(dependency.getVersion(), artifact.getModuleVersion().getId().getVersion()) + && artifact.getModuleVersion().getId().getGroup().equals(dependency.getGroup()); } private void queueConditionalDependency(ExtensionDependency extension, Dependency conditionalDep) { diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java index 5d0f37b1793e3..d68064a42277b 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/dependency/DependencyUtils.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Properties; import org.gradle.api.GradleException; @@ -73,48 +74,59 @@ public static String asDependencyNotation(ArtifactCoords artifactCoords) { return String.join(":", artifactCoords.getGroupId(), artifactCoords.getArtifactId(), artifactCoords.getVersion()); } - public static ExtensionDependency getExtensionInfoOrNull(Project project, ResolvedArtifact artifact) { - ModuleVersionIdentifier artifactId = artifact.getModuleVersion().getId(); - File artifactFile = artifact.getFile(); - - if (artifact.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier) { - final Project projectDep = project.getRootProject().findProject( - ((ProjectComponentIdentifier) artifact.getId().getComponentIdentifier()).getProjectPath()); - SourceSetContainer sourceSets = projectDep == null ? null - : projectDep.getExtensions().findByType(SourceSetContainer.class); - if (sourceSets != null) { - SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); - File resourcesDir = mainSourceSet.getOutput().getResourcesDir(); - Path descriptorPath = resourcesDir.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, projectDep); - } - } - } + public static Optional getOptionalExtensionInfo(Project project, ResolvedArtifact artifact) { + return loadExtensionDependencyFromProject(artifact, project) + .or(() -> loadExtensionDependencyFromDir(artifact, project)) + .or(() -> loadExtensionDependencyFromJar(artifact, project)); + } - if (!artifactFile.exists()) { - return null; - } - if (artifactFile.isDirectory()) { - Path descriptorPath = artifactFile.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, null); - } - } else if (ArtifactCoords.TYPE_JAR.equals(artifact.getExtension())) { - try (FileSystem artifactFs = ZipUtils.newFileSystem(artifactFile.toPath())) { - Path descriptorPath = artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(project, descriptorPath, artifactId, null); - } - } catch (IOException e) { - throw new GradleException("Failed to read " + artifactFile, e); - } - } - return null; + private static Optional loadExtensionDependencyFromProject(ResolvedArtifact artifact, + Project project) { + Optional projectDep = Optional.of(artifact.getId().getComponentIdentifier()) + .filter(ProjectComponentIdentifier.class::isInstance) + .map(ProjectComponentIdentifier.class::cast) + .map(ProjectComponentIdentifier::getProjectPath) + .map(projectPath -> project.getRootProject().findProject(projectPath)); + + return projectDep + .map(Project::getExtensions) + .map(container -> container.findByType(SourceSetContainer.class)) + .map(container -> container.findByName(SourceSet.MAIN_SOURCE_SET_NAME)) + .map(it -> it.getOutput().getResourcesDir()) + .map(File::toPath) + .flatMap(resourceDir -> loadOptionalExtensionInfo(project, resourceDir, artifact.getModuleVersion().getId(), + projectDep.get())); + } + + private static Optional loadExtensionDependencyFromDir(ResolvedArtifact artifact, Project project) { + return Optional.of(artifact.getFile().toPath()).filter(Files::exists) + .flatMap(path -> loadOptionalExtensionInfo(project, path, artifact.getModuleVersion().getId(), null)); + } + + private static Optional loadExtensionDependencyFromJar(ResolvedArtifact artifact, Project project) { + return Optional.of(artifact) + .filter(it -> ArtifactCoords.TYPE_JAR.equals(it.getExtension())) + .filter(it -> Files.exists(it.getFile().toPath())) + .flatMap(it -> { + try (FileSystem artifactFs = ZipUtils.newFileSystem(it.getFile().toPath())) { + return loadOptionalExtensionInfo(project, artifactFs.getPath(""), artifact.getModuleVersion().getId(), + null); + } catch (IOException e) { + throw new GradleException("Failed to read " + it.getFile(), e); + } + }); + } + + private static Optional loadOptionalExtensionInfo(Project project, Path resourcePath, + ModuleVersionIdentifier extensionId, Project extensionProject) { + return Optional.of(resourcePath) + .map(path -> path.resolve(BootstrapConstants.DESCRIPTOR_PATH)) + .filter(Files::exists) + .map(descriptorPath -> loadExtensionInfo(project, descriptorPath, extensionId, extensionProject)); } private static ExtensionDependency loadExtensionInfo(Project project, Path descriptorPath, - ModuleVersionIdentifier exentionId, Project extensionProject) { + ModuleVersionIdentifier extensionId, Project extensionProject) { final Properties extensionProperties = new Properties(); try (BufferedReader reader = Files.newBufferedReader(descriptorPath)) { extensionProperties.load(reader); @@ -138,10 +150,10 @@ private static ExtensionDependency loadExtensionInfo(Project project, Path descr final ArtifactKey[] constraints = BootstrapUtils .parseDependencyCondition(extensionProperties.getProperty(BootstrapConstants.DEPENDENCY_CONDITION)); if (extensionProject != null) { - return new LocalExtensionDependency(extensionProject, exentionId, deploymentModule, conditionalDependencies, + return new LocalExtensionDependency(extensionProject, extensionId, deploymentModule, conditionalDependencies, constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); } - return new ExtensionDependency(exentionId, deploymentModule, conditionalDependencies, + return new ExtensionDependency(extensionId, deploymentModule, conditionalDependencies, constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java index d3529126378ca..7815865536442 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java @@ -37,8 +37,6 @@ import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.Dependency; -import io.quarkus.maven.dependency.GACT; -import io.quarkus.maven.dependency.GACTV; import io.quarkus.maven.dependency.ResolvedArtifactDependency; import io.quarkus.runtime.LaunchMode; import io.smallrye.common.expression.Expression; @@ -59,7 +57,7 @@ public class QuarkusBootstrapProvider implements Closeable { .concurrencyLevel(4).softValues().initialCapacity(10).build(); static ArtifactKey getProjectId(MavenProject project) { - return new GACT(project.getGroupId(), project.getArtifactId()); + return ArtifactKey.ga(project.getGroupId(), project.getArtifactId()); } public RepositorySystem repositorySystem() { @@ -209,12 +207,13 @@ private CuratedApplication doBootstrap(QuarkusBootstrapMojo mojo, LaunchMode mod final List localProjects = mojo.mavenProject().getCollectedProjects(); final Set localProjectKeys = new HashSet<>(localProjects.size()); for (MavenProject p : localProjects) { - localProjectKeys.add(new GACT(p.getGroupId(), p.getArtifactId())); + localProjectKeys.add(ArtifactKey.ga(p.getGroupId(), p.getArtifactId())); } reloadableModules = new HashSet<>(localProjects.size() + 1); for (Artifact a : mojo.mavenProject().getArtifacts()) { - if (localProjectKeys.contains(new GACT(a.getGroupId(), a.getArtifactId()))) { - reloadableModules.add(new GACT(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getType())); + if (localProjectKeys.contains(ArtifactKey.ga(a.getGroupId(), a.getArtifactId()))) { + reloadableModules + .add(ArtifactKey.of(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getType())); } } reloadableModules.add(appArtifact.getKey()); @@ -228,7 +227,6 @@ private CuratedApplication doBootstrap(QuarkusBootstrapMojo mojo, LaunchMode mod } catch (AppModelResolverException e) { throw new MojoExecutionException("Failed to bootstrap application in " + mode + " mode", e); } - QuarkusBootstrap.Builder builder = QuarkusBootstrap.builder() .setAppArtifact(appModel.getAppArtifact()) .setExistingModel(appModel) @@ -237,6 +235,7 @@ private CuratedApplication doBootstrap(QuarkusBootstrapMojo mojo, LaunchMode mod .setBuildSystemProperties(effectiveProperties) .setProjectRoot(mojo.baseDir().toPath()) .setBaseName(mojo.finalName()) + .setOriginalBaseName(mojo.mavenProject().getBuild().getFinalName()) .setTargetDirectory(mojo.buildDir().toPath()) .setForcedDependencies(forcedDependencies); @@ -277,12 +276,12 @@ protected CuratedApplication bootstrapApplication(QuarkusBootstrapMojo mojo, Lau return prodApp == null ? prodApp = doBootstrap(mojo, mode) : prodApp; } - protected GACTV managingProject(QuarkusBootstrapMojo mojo) { + protected ArtifactCoords managingProject(QuarkusBootstrapMojo mojo) { if (mojo.appArtifactCoords() == null) { return null; } final Artifact artifact = mojo.mavenProject().getArtifact(); - return new GACTV(artifact.getGroupId(), artifact.getArtifactId(), + return ArtifactCoords.of(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getArtifactHandler().getExtension(), artifact.getVersion()); } @@ -321,7 +320,7 @@ private ArtifactCoords appArtifact(QuarkusBootstrapMojo mojo) } final String groupId = coordsArr[0]; final String artifactId = coordsArr[1]; - String classifier = ""; + String classifier = ArtifactCoords.DEFAULT_CLASSIFIER; String type = ArtifactCoords.TYPE_JAR; String version = null; if (coordsArr.length == 3) { @@ -349,7 +348,7 @@ private ArtifactCoords appArtifact(QuarkusBootstrapMojo mojo) } } - return new GACTV(groupId, artifactId, classifier, type, version); + return ArtifactCoords.of(groupId, artifactId, classifier, type, version); } @Override diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc index 4128b0c53197d..c92d25fe88144 100644 --- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc +++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc @@ -1052,7 +1052,7 @@ kubectl port-forward svc/ 5005:5005 Using this command, you'll forward the traffic from the "localhost:5005" to the kubernetes service running the java agent using the port "5005" which is the one that the java agent uses by default for remote debugging. You can also configure another java agent port using the property `quarkus.kubernetes.remote-debug.address-port`. -Finally, all you need to do is to configure your favorite IDE to attach the java agent process that is forwarded to `localhost:5005` and start to debug your application. For example, in IntelliJ IDEA, you can follow https://www.jetbrains.com/help/idea/tutorial-remote-debug.html:[this tutorial] to debug remote applications. +Finally, all you need to do is to configure your favorite IDE to attach the java agent process that is forwarded to `localhost:5005` and start to debug your application. For example, in IntelliJ IDEA, you can follow https://www.jetbrains.com/help/idea/tutorial-remote-debug.html[this tutorial] to debug remote applications. == Using existing resources diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index 8859677edd6dc..5008778c42402 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -815,7 +815,7 @@ public class RaceWeight { } // Only the race and the average weight will be loaded -PanacheQuery query = Person.find("select d.race, AVG(d.weight) from Dog d group by d.race).project(RaceWeight.class); +PanacheQuery query = Person.find("select d.race, AVG(d.weight) from Dog d group by d.race").project(RaceWeight.class); ---- <1> Hibernate ORM will use this constructor. When the query has a select clause, it is possible to have multiple constructors. @@ -827,7 +827,7 @@ For example, this will fail: [source,java] ---- -PanacheQuery query = Person.find("select new MyView(d.race, AVG(d.weight)) from Dog d group by d.race).project(AnotherView.class); +PanacheQuery query = Person.find("select new MyView(d.race, AVG(d.weight)) from Dog d group by d.race").project(AnotherView.class); ---- ==== diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 0ecb30bb527e2..3633a29e46301 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -127,8 +127,8 @@ Your `application.properties` would then look like this: [source,properties] ---- -quarkus.http.ssl.certificate.file=/path/to/certificate -quarkus.http.ssl.certificate.key-file=/path/to/key +quarkus.http.ssl.certificate.files=/path/to/certificate +quarkus.http.ssl.certificate.key-files=/path/to/key ---- === Providing a keystore diff --git a/docs/src/main/asciidoc/javascript/config.js b/docs/src/main/asciidoc/javascript/config.js index 22e453b5b853c..898d7643f61cf 100644 --- a/docs/src/main/asciidoc/javascript/config.js +++ b/docs/src/main/asciidoc/javascript/config.js @@ -296,6 +296,11 @@ function makeCollapsibleHandler(descDiv, td, row, return; } + // don't collapse if the target is button with attribute "do-not-collapse" + if( (target.localName == 'button' && target.hasAttribute("do-not-collapse"))) { + return; + } + var isCollapsed = descDiv.classList.contains('description-collapsed'); if( isCollapsed ) { collapsibleSpan.childNodes.item(0).nodeValue = 'Show less'; diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc index 054de5e36cda3..8c06be867948b 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc @@ -610,6 +610,81 @@ public class MovieResourceTest { } ---- +[[apicurio-versions-compatibility]] +== Using compatible versions of the Apicurio Registry + +The `quarkus-apicurio-registry-avro` extension depends on recent versions of Apicurio Registry client, +and most versions of Apicurio Registry server and client are backwards compatible. +For some you need to make sure that the client used by Serdes is compatible with the server. + +For example, with Apicurio dev service if you set the image name to use version `2.1.5.Final`: + +[source,properties] +---- +quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.1.5.Final +---- + +You need to make sure that `apicurio-registry-serdes-avro-serde` dependency +and the REST client `apicurio-common-rest-client-vertx` dependency are set to compatible versions: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-apicurio-registry-avro + + + io.apicurio + apicurio-common-rest-client-vertx + + + io.apicurio + apicurio-registry-serdes-avro-serde + + + + + io.apicurio + apicurio-registry-serdes-avro-serde + 2.1.5.Final + + + io.apicurio + apicurio-common-rest-client-jdk + + + + + io.apicurio + apicurio-common-rest-client-vertx + 0.1.5.Final + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +dependencies { + implementation(platform("io.quarkus.platform:quarkus-bom:2.12.3.Final")) + + ... + + implementation("io.quarkus:quarkus-apicurio-registry-avro") + implementation("io.apicurio:apicurio-registry-serdes-avro-serde") { + exclude group: "io.apicurio", module: "apicurio-common-rest-client-jdk" + version { + strictly "2.1.5.Final" + } + } + implementation("io.apicurio:apicurio-common-rest-client-vertx") { + version { + strictly "0.1.5.Final" + } + } +} +---- + [[confluent]] == Using the Confluent Schema Registry diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index efb06fd119ec2..a8281d1280a9a 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -282,7 +282,7 @@ Thus, you can use both. The first one provides more fine-grained tuning such as the worker pool to use and whether it preserves the order. The second one, used also with other reactive features of Quarkus, uses the default worker pool and preserves the order. -Detailed information on the usage of `@Blocking` annotation can be found in https://smallrye.io/smallrye-reactive-messaging/smallrye-reactive-messaging/3.1/advanced/blocking.html[SmallRye Reactive Messaging – Handling blocking execution]. +Detailed information on the usage of `@Blocking` annotation can be found in https://smallrye.io/smallrye-reactive-messaging/latest/concepts/blocking/[SmallRye Reactive Messaging – Handling blocking execution]. ==== [TIP] @@ -364,6 +364,9 @@ If high throughput is important for you, and you are not limited by the downstre - or set `enable.auto.commit` to true and annotate the consuming method with `@Acknowledgment(Acknowledgment.Strategy.NONE)`. ==== +Smallrye Reactive Messaging enables implementing custom commit strategies. +See https://smallrye.io/smallrye-reactive-messaging/latest/kafka/receiving-kafka-records/#acknowledgement[SmallRye Reactive Messaging documentation] for more information. + [[error-handling]] === Error Handling Strategies @@ -389,6 +392,9 @@ The record written on the dead letter queue contains a set of additional headers - *dead-letter-partition*: the original partition of the record (integer mapped to String) - *dead-letter-offset*: the original offset of the record (long mapped to String) +Smallrye Reactive Messaging enables implementing custom failure strategies. +See https://smallrye.io/smallrye-reactive-messaging/latest/kafka/receiving-kafka-records/#acknowledgement[SmallRye Reactive Messaging documentation] for more information. + ==== Retrying processing You can combine Reactive Messaging with https://github.com/smallrye/smallrye-fault-tolerance[SmallRye Fault Tolerance], and retry processing if it failed: @@ -926,7 +932,18 @@ The `io.smallrye.reactive.messaging.annotations.Emitter`, `io.smallrye.reactive. The new `Emitter.send` method returns a `CompletionStage` completed when the produced message is acknowledged. ==== -More information on how to use `Emitter` can be found in https://smallrye.io/smallrye-reactive-messaging/smallrye-reactive-messaging/3.1/emitter/emitter.html#_emitter_and_channel[SmallRye Reactive Messaging – Emitters and Channels] +[NOTE] +.Depreciation +==== +`MutinyEmitter#send(Message msg)` method is deprecated in favor of following methods receiving `Message` for emitting: + +* `> Uni sendMessage(M msg)` +* `> void sendMessageAndAwait(M msg)` +* `> Cancellable sendMessageAndForget(M msg)` + +==== + +More information on how to use `Emitter` can be found in https://smallrye.io/smallrye-reactive-messaging/latest/concepts/emitter/[SmallRye Reactive Messaging – Emitters and Channels] === Write Acknowledgement @@ -1254,11 +1271,6 @@ The `KafkaTransactions#withTransactionAndAck` method acks and nacks the message Nacked messages will be handled by the failure strategy of the incoming channel, (see <>). Configuring `failure-strategy=ignore` simply resets the Kafka consumer to the last committed offsets and resumes the consumption from there. -[NOTE] -==== -Redpanda does not yet support link:https://github.com/redpanda-data/redpanda/issues/3279[producer scalability for exactly-once processing]. -In order to use Kafka exactly-once processing with Quarkus you can configure Dev Services for Kafka to <>. -==== [[kafka-bare-clients]] == Accessing Kafka clients directly @@ -1996,10 +2008,14 @@ As described in <>, you need to add the `@Blocking` annotat See the xref:quarkus-reactive-architecture.adoc[Quarkus Reactive Architecture documentation] for further details on this topic. +== Channel Decorators + +SmallRye Reactive Messaging supports decorating incoming and outgoing channels for implementing cross-cutting concerns such as monitoring, tracing or message interception. For more information on implementing decorators and message interceptors see the http://smallrye.io/smallrye-reactive-messaging/3.19.1/concepts/decorators/[SmallRye Reactive Messaging documentation]. + [[kafka-configuration]] == Configuration Reference -More details about the SmallRye Reactive Messaging configuration can be found in the https://smallrye.io/smallrye-reactive-messaging/smallrye-reactive-messaging/3.1/kafka/kafka.html[SmallRye Reactive Messaging - Kafka Connector Documentation]. +More details about the SmallRye Reactive Messaging configuration can be found in the https://smallrye.io/smallrye-reactive-messaging/latest/kafka/kafka/#using-the-kafka-connector[SmallRye Reactive Messaging - Kafka Connector Documentation]. [TIP] ==== @@ -2336,7 +2352,7 @@ public class FruitStore { Mutiny.Session session; // <1> @Incoming("in") - public Uni consume(Fruit fruit) { + public Uni consume(Fruit entity) { return session.withTransaction(t -> { // <2> return entity.persistAndFlush() // <3> .replaceWithVoid(); // <4> diff --git a/docs/src/main/asciidoc/opentracing.adoc b/docs/src/main/asciidoc/opentracing.adoc index 3c5e6d83c84f8..2905ffb25afc2 100644 --- a/docs/src/main/asciidoc/opentracing.adoc +++ b/docs/src/main/asciidoc/opentracing.adoc @@ -4,12 +4,22 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// = Using OpenTracing +:extension-status: deprecated include::./attributes.adoc[] This guide explains how your Quarkus application can utilize OpenTracing to provide distributed tracing for interactive web applications. +[IMPORTANT] +==== +xref:opentelemetry.adoc[OpenTelemetry] is the recommended approach to tracing and telemetry for Quarkus. + +When Quarkus will upgrade to Eclipse MicroProfile 6, the SmallRye OpenTracing support will be discontinued. +==== + +include::{includes}/extension-status.adoc[] + == Prerequisites :prerequisites-docker: diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index 0fcfce0d6d71e..b3d32086fbf5b 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -498,29 +498,6 @@ public class ReportGenerator { <2> Use the `@Scheduled` annotation to instruct Quarkus to execute this method on the half hour. For more information see the xref:scheduler.adoc[Scheduler] guide. <3> The `TemplateInstance.render()` method triggers rendering. Note that this method blocks the current thread. -== Reactive and Asynchronous APIs - -Templates can be rendered as a `CompletionStage` (completed with the rendered output asynchronously) or as `Publisher` containing the rendered chunks: - -[source, java] ----- -CompletionStage async = template.data("name", "neo").renderAsync(); -Publisher publisher = template.data("name", "neo").publisher(); ----- - -In the case of a `Publisher`, the template is rendered chunk by chunk following the requests from the subscriber. -The rendering is not started until a subscriber requests it. -The returned `Publisher` is an instance of `io.smallrye.mutiny.Multi`. - -It is possible to create an instance of `io.smallrye.mutiny.Uni` as follows: - -[source, java] ----- -Uni uni = Uni.createFrom().completionStage(() -> template.data("name", "neo").renderAsync()); ----- - -In this case, the rendering only starts once the subscriber requests it. - == Qute Reference Guide To learn more about Qute, please refer to the xref:qute-reference.adoc[Qute reference guide]. diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index 0d5f969364a0f..5976003d40733 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -713,7 +713,7 @@ public static class MyExampleCustomizer implements RedisOptionsCustomizer { === Dev Services -See link:redis-dev-services.adoc[Redis Dev Service]. +See xref:redis-dev-services.adoc[Redis Dev Service]. [[redis-configuration-reference]] == Configuration Reference diff --git a/docs/src/main/asciidoc/redis.adoc b/docs/src/main/asciidoc/redis.adoc index cedbf72431104..57a2e59c3372f 100644 --- a/docs/src/main/asciidoc/redis.adoc +++ b/docs/src/main/asciidoc/redis.adoc @@ -526,4 +526,4 @@ Once the build is finished, you can run the executable with: == Going further -To learn more about the Quarkus Redis extension, check link:redis-reference.adoc[the Redis extension reference guide]. +To learn more about the Quarkus Redis extension, check xref:redis-reference.adoc[the Redis extension reference guide]. diff --git a/docs/src/main/asciidoc/security-csrf-prevention.adoc b/docs/src/main/asciidoc/security-csrf-prevention.adoc index 376c5934a5469..e877219bf1b54 100644 --- a/docs/src/main/asciidoc/security-csrf-prevention.adoc +++ b/docs/src/main/asciidoc/security-csrf-prevention.adoc @@ -7,9 +7,9 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::./attributes.adoc[] -https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery(CSRF)] is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. +https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery (CSRF)] is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. -Quarkus Security provides a CSRF prevention feature which consists of a xref:resteasy-reactive.adoc[Resteasy Reactive] server filter which creates and verifies CSRF tokens and an HTML form parameter provider which supports the xref:qute-reference.adoc#injecting-beans-directly-in-templates[injection of CSRF tokens in Qute templates]. +Quarkus Security provides a CSRF prevention feature which consists of a xref:resteasy-reactive.adoc[RESTEasy Reactive] server filter which creates and verifies CSRF tokens and an HTML form parameter provider which supports the xref:qute-reference.adoc#injecting-beans-directly-in-templates[injection of CSRF tokens in Qute templates]. == Creating the Project @@ -45,7 +45,7 @@ This will add the following to your build file: implementation("io.quarkus:quarkus-csrf-reactive") ---- -Next lets add a Qute template producing an HTML form: +Next, let's add a `csrfToken.html` Qute template producing an HTML form in the `src/main/resources/templates` folder: [source,html] ---- @@ -67,11 +67,8 @@ Next lets add a Qute template producing an HTML form: ---- - <1> This expression is used to inject a CSRF token into a hidden form field. This token will be verified by the CSRF filter against a CSRF cookie. -You can name the file containing this template as `csrfToken.html` and put it in a `src/main/resources/templates` folder. - Now let's create a resource class which returns an HTML form and handles form POST requests: [source,java] @@ -107,19 +104,18 @@ public class UserNameResource { @Path("/csrfTokenForm") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_PLAIN) - public String postCsrfTokenForm(@FormParam("name") String name) { + public String postCsrfTokenForm(@FormParam("name") String userName) { return userName; <3> } } ---- - <1> Inject the `csrfToken.html` as a `Template`. -<2> Return HTML form with a hidden form field containing a CSRF token created by the CSRF filter. -<3> Handle the form POST request, this method can only be invoked only if the CSRF filter has successfully verified the token. +<2> Return the HTML form with a hidden form field containing a CSRF token created by the CSRF filter. +<3> Handle the form POST request, this method can only be invoked if the CSRF filter has successfully verified the token. The form POST request will fail with HTTP status `400` if the filter finds the hidden CSRF form field is missing, the CSRF cookie is missing, or if the CSRF form field and CSRF cookie values do not match. -At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to `csrf_token`, and the filter will verify the token. But lets change these names: +At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to `csrf_token`, and the filter will verify the token. But let's change these names: [source,properties] ---- @@ -127,7 +123,7 @@ quarkus.csrf-reactive.form-field-name=csrftoken quarkus.csrf-reactive.cookie-name=csrftoken ---- -Note that the CSRF filter has to read the input stream in order to verify the token and then re-create the stream for the application code to read it as well. The filter performs this work on an event loop thread so for small form payloads such as the one shown in the example above it will have negligible peformance side-effects. However if you deal with large form payloads then it is recommended to compare the CSRF form field and cookie values in the application code: +Note that the CSRF filter has to read the input stream in order to verify the token and then re-create the stream for the application code to read it as well. The filter performs this work on an event loop thread so for small form payloads, such as the one shown in the example above, it will have negligible performance side-effects. However if you deal with large form payloads then it is recommended to compare the CSRF form field and cookie values in the application code: [source,java] ---- @@ -172,7 +168,6 @@ public class UserNameResource { } } ---- - <1> Compare the CSRF form field and cookie values and fail with HTTP status `400` if they don't match. Also disable the token verification in the filter: @@ -182,7 +177,6 @@ Also disable the token verification in the filter: quarkus.csrf-reactive.verify-token=false ---- - [[csrf-reactive-configuration-reference]] == Configuration Reference diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 9dcd5aebf7359..eb80ac9ee6a6b 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -535,7 +535,7 @@ or with the secret retrieved from a xref:credentials-provider.adoc[CredentialsPr quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ quarkus.oidc-client.client-id=quarkus-app -# This is a key which will be used to retrieve a secret from the map of credentails returned from CredentialsProvider +# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider quarkus.oidc-client.credentials.client-secret.provider.key=mysecret-key # Set it only if more than one CredentialsProvider can be registered quarkus.oidc-client.credentials.client-secret.provider.name=oidc-credentials-provider diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 2987891b95f72..89ddb68c619e8 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -3,161 +3,186 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// -= Security Architecture and Guides += Quarkus Security overview include::./attributes.adoc[] -Quarkus Security provides the architecture, multiple authentication and authorization mechanisms, and other tools for the developers to build a production-quality security for their Quarkus applications. +Quarkus Security is a framework that provides the architecture, multiple authentication and authorization mechanisms, and other tools for you to build secure and production-quality Java applications. -This document provides a brief overview of Quarkus Security and links to the individual guides. +== Getting started with Quarkus Security -== Getting Started +Before you start building security into your Quarkus applications, review the overview information to learn about the Quarkus Security architecture and the different authentication and authorization mechanisms that Quarkus supports. -Please see the xref:security-getting-started.adoc[Getting Started With Security] guide for a quick walkthrough through Quarkus Security where you can learn how to use xref:security-basic-auth-concept.adoc[Basic HTTP Authentication] mechanism and `JPA Identity Provider` to create `SecurityIdentity` and authorize a secure access to the endpoint with `Role Based Access Control`. +To get started with security in Quarkus, we recommend that you first combine the Quarkus built-in xref:security-basic-auth-concept.adoc[Basic HTTP authentication] with the JPA identity provider to enable role-based access control (RBAC). +Complete the steps in the ref:security-getting-started.adoc[Secure a Quarkus application with Basic authentication] tutorial. +After you have successfully secured your Quarkus application with basic HTTP authentication, you can increase the security further by adding more advanced authentication mechanisms, for example, OpenID Connect (OIDC) authentication. -== Architecture +== Security architecture -`HttpAuthenticationMechanism` is the main entry into Quarkus HTTP Security. +The `HttpAuthenticationMechanism` interface is the main entry mechanism for securing HTTP applications in Quarkus. -Quarkus Security Manager uses `HttpAuthenticationMechanism` to extract the authentication credentials from the HTTP request and delegates to `IdentityProvider` to -complete the conversion of these credentials to `SecurityIdentity`. +Quarkus Security uses `HttpAuthenticationMechanism` to extract the authentication credentials from the HTTP request and delegates them to `IdentityProvider` to convert the credentials to `SecurityIdentity`. +For example, the credentials can come from the `Authorization` header, client HTTPS certificates, or cookies. -For example, the credentials may be coming with the HTTP `Authorization` header, client HTTPS certificates or cookies. - -`IdentityProvider` verifies the authentication credentials and maps them to `SecurityIdentity` which contains the username, roles, the original authentication credentials, and other attributes. +`IdentityProvider` verifies the authentication credentials and maps them to `SecurityIdentity`, which has the username, roles, original authentication credentials, and other attributes. For every authenticated resource, you can inject a `SecurityIdentity` instance to get the authenticated identity information. -In some other contexts you may have other parallel representations of the same information (or parts of it) such as `SecurityContext` -for JAX-RS or `JsonWebToken` for JWT. +In other contexts, it is possible to have other parallel representations of the same information or parts of it, for example, `SecurityContext` +for JAX-RS or `JsonWebToken` for JSON Web Tokens (JWT). == Authentication mechanisms -Quarkus supports several sources to load authentication information from. - -=== Basic and Form Authentication Mechanisms - -Basic and Form HTTP-based authentication mechanisms are the core authentication mechanisms supported in Quarkus. -Please see xref:security-basic-auth-concept.adoc[Basic HTTP Authentication] and xref:security-built-in-authentication.adoc#form-auth[Form HTTP Authentication] for more information. +Quarkus supports multiple authentication mechanisms -=== WebAuthn Authentication Mechanism +=== Basic and Form HTTP authentication -https://webauthn.guide/[WebAuthn] is an authentication mechanism designed to replace passwords. In short, every -time you write a service for registering new users, or logging them in, instead of asking for a password, you use WebAuthn, which will replace the password. +xref:security-basic-auth-concept.adoc[Basic HTTP Authentication] and xref:security-built-in-authentication.adoc#form-auth[Form HTTP authentication] are the core authentication mechanisms supported in Quarkus. -Please see xref:security-webauthn.adoc[our dedicated WebAuthn documentation] for more information. +=== WebAuthn authentication -=== Mutual TLS Authentication +https://webauthn.guide/[WebAuthn] is an authentication mechanism that replaces passwords. +When you write a service for registering new users, or logging them in, instead of asking for a password, you can use WebAuthn, which replaces the password. +For more information, see xref:security-webauthn.adoc[Secure a Quarkus application by using the WebAuthn authentication mechanism]. -Quarkus provides Mutual TLS authentication so that you can authenticate users based on their X.509 certificates. +=== Mutual TLS (mTLS) authentication -Please see xref:security-built-in-authentication.adoc#mutual-tls[Mutual TLS Authentication] for more information. +Quarkus provides mutual TLS (mTLS) authentication so that you can authenticate users based on their X.509 certificates. +For more information, see xref:security-built-in-authentication.adoc#mutual-tls[mutual TLS authentication]. -=== OpenID Connect +=== OpenID Connect authentication -OpenID Connect (OIDC) is an identity layer that works on top of the OAuth 2.0 protocol. OIDC enables client applications to verify the identity of a user based on authentication that is performed by the OIDC provider and retrieves basic information about that user. +OpenID Connect (OIDC) is an identity layer that works on top of the OAuth 2.0 protocol. OIDC enables client applications to verify the identity of a user based on the authentication performed by the OIDC provider and to retrieve basic information about that user. The Quarkus `quarkus-oidc` extension provides a reactive, interoperable, multitenant-enabled OIDC adapter that supports Bearer Token and Authorization Code Flow authentication mechanisms. - The Bearer Token mechanism extracts the token from the HTTP Authorization header. -The Authorization Code Flow mechanism redirects the user to an OIDC provider to authenticate the identity of this user and, after the user is redirected back to Quarkus, the mechanism completes the authentication process by exchanging the provided code grant for ID, access, and refresh tokens. +The Authorization Code Flow mechanism redirects the user to an OIDC provider to authenticate the identity of the user. +After the user is redirected back to Quarkus, the mechanism completes the authentication process by exchanging the provided code that was granted for the ID, access, and refresh tokens. -You can verify ID and access JSON Web Token (JWT) tokens by using the refreshable JSON Web Key (JWK) set. However, both JWT and opaque (binary) tokens can be introspected remotely. +You can verify ID and access JWT tokens by using the refreshable JSON Web Key (JWK) set or you can introspect them remotely. +However, opaque (binary) tokens can only be introspected remotely. [NOTE] ==== -Using the Quarkus OIDC extension, both Bearer Token and Authorization Code Flow mechanisms use <> to represent JWT tokens as Microprofile JWT `org.eclipse.microprofile.jwt.JsonWebToken`. +Using the Quarkus OIDC extension, both Bearer Token and Authorization Code Flow mechanisms use <> to represent JWT tokens as MicroProfile JWT `org.eclipse.microprofile.jwt.JsonWebToken`. ==== -For information about the Bearer Token authentication mechanism, see xref:security-openid-connect.adoc[Using OpenID Connect to Protect Service Applications]. - -For information about the Authorization Code Flow authentication mechanism, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Application]. - -For information about multiple tenants that can support Bearer Token or Authorization Code Flow mechanisms, see xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy]. +==== Additional Quarkus resources for OIDC authentication -For information about using Keycloak to Centralize Authorization, see the xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization] guide. +For more information about OIDC authentication and authorization methods you can use to secure your Quarkus applications, see the following detailed resources: -For information about configuring Keycloak programmatically, see the xref:security-keycloak-admin-client.adoc[Keycloak Admin Client] guide. +[options="header"] +|==== +|OIDC topic |Quarkus information resource +|Bearer Token authentication mechanism|xref:security-openid-connect.adoc[Using OpenID Connect (OIDC) to protect service applications using Bearer Token authorization] +|Authorization Code Flow authentication mechanism|xref:security-openid-connect-web-authentication.adoc[OpenID Connect (OIDC) authorization code flow mechanism] +|Multiple tenants that can support Bearer Token or Authorization Code Flow mechanisms|xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect (OIDC) multi-tenancy] +|Using Keycloak to centralize authorization|xref:security-keycloak-authorization.adoc[Using OpenID Connect (OIDC) and Keycloak to centralize authorization] +|Configuring Keycloak programmatically|xref:security-keycloak-admin-client.adoc[Using the Keycloak admin client] +|==== [NOTE] ==== -* If you need to enable the Quarkus OIDC extension at runtime, set `quarkus.oidc.tenant-enabled=false` at build time then re-enable it at runtime by using a system property. -For more information about managing the individual tenant configurations in multitenant OIDC deployments, see xref:security-openid-connect-multitenancy.adoc#disable-tenant[Disabling Tenant Configurations]. +* If you need to enable the Quarkus OIDC extension at runtime, set `quarkus.oidc.tenant-enabled=false` at build time and then re-enable it at runtime by using a system property. +For more information about managing the individual tenant configurations in multitenant OIDC deployments, see the _Disabling tenant configurations_ section in the xref:security-openid-connect-multitenancy.adoc#disable-tenant[Using OpenID Connect (OIDC) multi-tenancy] guide. ==== -=== OpenID Connect Client and Filters +=== OpenID Connect client and filters -`quarkus-oidc-client` extension provides `OidcClient` for acquiring and refreshing access tokens from OpenID Connect and OAuth2 providers which support `client-credentials`, `password` and `refresh_token` token grants. +The `quarkus-oidc-client` extension provides `OidcClient` for acquiring and refreshing access tokens from OpenID Connect and OAuth2 providers that support the following token grants: +* `client-credentials` +* `password` +* `refresh_token` -`quarkus-oidc-client-filter` extension depends on the `quarkus-oidc-client` extension and provides JAX-RS `OidcClientRequestFilter` which sets the access token acquired by `OidcClient` as an HTTP `Authorization` header's `Bearer` scheme value. This filter can be registered with MP RestClient implementations injected into the current Quarkus endpoint, but it is not related to the authentication requirements of this service endpoint. For example, it can be a public endpoint, or it can be protected with MTLS - the important point is that this Quarkus endpoint does not have to be protected itself with the Quarkus OpenID Connect adapter. +The `quarkus-oidc-client-filter` extension requires the `quarkus-oidc-client` extension and provides JAX-RS `OidcClientRequestFilter`, which sets the access token acquired by `OidcClient` as the `Bearer` scheme value of the HTTP `Authorization` header. +This filter can be registered with MP RestClient implementations injected into the current Quarkus endpoint, but it is not related to the authentication requirements of this service endpoint. +For example, it can be a public endpoint, or it can be protected with mTLS. -`quarkus-oidc-token-propagation` extension depends on the `quarkus-oidc` extension and provides JAX-RS `TokenCredentialRequestFilter` which sets the OpenID Connect Bearer or Authorization Code Flow access token as an HTTP `Authorization` header's `Bearer` scheme value. This filter can be registered with MP RestClient implementations injected into the current Quarkus endpoint and the Quarkus endpoint must be protected itself with the Quarkus OpenID Connect adapter. This filter can be used to propagate the access token to the downstream services. - -See the xref:security-openid-connect-client.adoc[OpenID Connect and Token Propagation Quickstart] and xref:security-openid-connect-client-reference.adoc[OpenID Connect and OAuth2 Client Reference] guides for more information. +[IMPORTANT] +==== +In this scenario, you do not need to protect your Quarkus endpoint by using the Quarkus OpenID Connect adapter. +==== -[[smallrye-jwt]] -=== SmallRye JWT +The `quarkus-oidc-token-propagation` extension requires the `quarkus-oidc` extension and provides JAX-RS `TokenCredentialRequestFilter`, which sets the OpenID Connect Bearer or Authorization Code Flow access token as the `Bearer` scheme value of the HTTP `Authorization` header. +This filter can be registered with MP RestClient implementations injected into the current Quarkus endpoint, which in turn must be protected by using the Quarkus OpenID Connect adapter. +This filter can be used to propagate the access token to the downstream services. -`quarkus-smallrye-jwt` provides Microprofile JWT 1.1.1 implementation and many more options to verify signed and encrypted `JWT` tokens and represent them as `org.eclipse.microprofile.jwt.JsonWebToken`. +For more information, see the xref:security-openid-connect-client.adoc[OpenID Connect client and token propagation quickstart] and xref:security-openid-connect-client-reference.adoc[OpenID Connect (OIDC) and OAuth2 client and filters reference] guides. -It provides an alternative to `quarkus-oidc` Bearer Token Authentication Mechanism. It can currently verify only `JWT` tokens using the PEM keys or refreshable `JWK` key set. +[[smallrye-jwt]] +=== SmallRye JWT authentication -Additionally, it provides `JWT Generation API` for creating `signed`, `inner-signed` and/or `encrypted` `JWT` tokens with ease. +The `quarkus-smallrye-jwt` extension provides a MicroProfile JSON Web Token (JWT) 1.2.1 implementation and multiple options to verify signed and encrypted `JWT` tokens and represents them as `org.eclipse.microprofile.jwt.JsonWebToken`. -See the xref:security-jwt.adoc[Using SmallRye JWT] guide for more information. +`quarkus-smallrye-jwt` is an alternative to the `quarkus-oidc` Bearer Token authentication mechanism, and verifies only `JWT` tokens by using either PEM keys or the refreshable `JWK` key set. +`quarkus-smallrye-jwt` also provides the JWT generation API, which you can use to easily create `signed`, `inner-signed`, and `encrypted` `JWT` tokens. -=== OAuth2 +For more information, see xref:security-jwt.adoc[Using SmallRye JWT role-based access control]. -`quarkus-elytron-security-oauth2` provides an alternative to `quarkus-oidc` Bearer Token Authentication Mechanism. It is based on `Elytron` and is primarily meant for introspecting the opaque tokens remotely. +=== OAuth2 authentication -See the xref:security-oauth2.adoc[Using OAuth2] guide for more information. +`quarkus-elytron-security-oauth2` provides an alternative to the `quarkus-oidc` Bearer Token authentication mechanism. `quarkus-elytron-security-oauth2` is based on `Elytron` and is primarily intended for introspecting opaque tokens remotely. +For more information, see xref:security-oauth2.adoc[Using OAuth2]. [[oidc-jwt-oauth2-comparison]] -=== Choosing between OpenID Connect, SmallRye JWT and OAuth2 extensions +=== Choosing between OpenID Connect, SmallRye JWT, and OAuth2 authentication mechanisms -`quarkus-oidc` extension requires an OpenID Connect provider such as Keycloak which can be used to verify the Bearer tokens or authenticate the end users with the Authorization Code flow. In both cases `quarkus-oidc` requires a connection to this OpenID Connect provider. +Use the following information to help you to decide which authentication mechanism to use to secure your Quarkus applications: -`quarkus-oidc` is the only option when the user authentication by using Authorization Code flow or supporting multiple tenants is required. It can also request a UserInfo using both Authorization Code Flow and Bearer access tokens. +* `quarkus-oidc` requires an OpenID Connect provider such as Keycloak, which can be used to verify the Bearer tokens or authenticate the end users with the Authorization Code flow. +In both cases, `quarkus-oidc` requires a connection to the specified OpenID Connect provider. -When the Bearer tokens have to be verified then `quarkus-oidc`, `quarkus-smallrye-jwt` and `quarkus-elytron-security-oauth2` can be used. +* If the user authentication requires Authorization Code flow or you need to support multiple tenants, use `quarkus-oidc`. +`quarkus-oidc` can also request user information by using both Authorization Code Flow and Bearer access tokens. -If you have Bearer tokens in a JWT format then all these 3 extensions can be used. Both `quarkus-oidc` and `quarkus-smallrye-jwt` support refreshing the JsonWebKey (JWK) set when the OpenID Connect provider rotates the keys, therefore `quarkus-oidc` or `quarkus-smallrye-jwt` should be used for verifying JWT tokens if the remote token introspection has to be avoided or not supported by the providers. +* If your Bearer tokens must be verified, use `quarkus-oidc`, `quarkus-smallrye-jwt`, or `quarkus-elytron-security-oauth2`. -`quarkus-smallrye-jwt` does not support the remote introspection of the opaque tokens or even JWT tokens - it always relies on the locally available keys - possibly fetched from the OpenID Connect provider. So if you need to introspect the JWT tokens remotely then both `quarkus-oidc` and `quarkus-elytron-security-oauth2` will work. Both extensions also support the verification of the opaque/binary tokens by using the remote introspection. +* If your Bearer tokens are in a JWT format, you can use either of the three extensions. Both `quarkus-oidc` and `quarkus-smallrye-jwt` support refreshing the JsonWebKey (JWK) set when the OpenID Connect provider rotates the keys. +Therefore, if remote token introspection must be avoided or is unsupported by the providers, use `quarkus-oidc` or `quarkus-smallrye-jwt` for verifying JWT tokens. -`quarkus-oidc` and `quarkus-smallrye-jwt` can have both JWT and opaque tokens injected into the endpoint code - the injected JWT tokens may offer a richer information about the user. All extensions can have the tokens injected as `Principal`. +* If you need to introspect the JWT tokens remotely, you can use either `quarkus-oidc` or `quarkus-elytron-security-oauth2` because they support the verification of the opaque or binary tokens by using remote introspection. +`quarkus-smallrye-jwt` does not support the remote introspection of both opaque or JWT tokens but instead relies on the locally available keys that are usually retrieved from the OpenID Connect provider. -`quarkus-smallrye-jwt` supports more key formats than `quarkus-oidc`. The latter will only use the JWK-formatted keys which are part of a JWK set. The former - can also work with PEM keys. +* `quarkus-oidc` and `quarkus-smallrye-jwt` support the injecting of JWT and opaque tokens into the endpoint code. +Injected JWT tokens provide more information about the user. All extensions can have the tokens injected as `Principal`. -`quarkus-smallrye-jwt` can handle locally not only signed but also inner-signed-and-encrypted or only encrypted tokens. In fact `quarkus-oidc` and `quarkus-elytron-security-oauth2` can verify such tokens too but only by treating them as opaque tokens and verifying them through the remote introspection. +* `quarkus-smallrye-jwt` supports more key formats than `quarkus-oidc`. `quarkus-oidc` uses only the JWK-formatted keys that are part of a JWK set, whereas `quarkus-smallrye-jwt` supports PEM keys. -`quarkus-elytron-security-oauth2` is the best choice if you need a lightweight library for the remote introspection of either opaque or JWT tokens. +* `quarkus-smallrye-jwt` handles locally signed, inner-signed-and-encrypted, and encrypted tokens. +While `quarkus-oidc` and `quarkus-elytron-security-oauth2` can also verify such tokens but treats them as opaque tokens and verifies them through remote introspection. -Note that a choice of using the opaque versus JWT token format is often driven by the architectural considerations. Opaque tokens are usually much shorter than JWT tokens, but they require maintaining most of the token associated state in the provider database - the opaque tokens are effectively the database pointers. JWT tokens are significantly longer than the opaque tokens - but the providers are effectively delegating storing most of the token associated state to the client by storing it as the token claims and either signing and/or encrypting them. +* If you need a lightweight library for the remote introspection of opaque or JWT tokens, use `quarkus-elytron-security-oauth2`. -Below is a summary of the options. +[NOTE] +==== +Your decision to choose whether to use opaque or JWT token format will be driven by architectural considerations. +Opaque tokens tend to be much shorter than JWT tokens but need most of the token-associated state to be maintained in the provider database. +Opaque tokens are effectively database pointers. +JWT tokens are significantly longer than the opaque tokens but the providers are effectively delegating storing most of the token-associated state to the client by storing it as the token claims and either signing or encrypting them. +==== + +The following table provides a summary of the options for each authentication mechanism: |=== | | quarkus-oidc| quarkus-smallrye-jwt | quarkus-elytron-security-oauth2 -|Bearer JWT verification is required -|Local Verification or Introspection -|Local Verification +|Requires Bearer JWT verification +|Local verification or introspection +|Local verification |Introspection -|Bearer Opaque Token verification is required +|Requires Bearer opaque token verification |Introspection |No |Introspection -|Refreshing JsonWebKey set for verifying JWT tokens +|Refreshing `JsonWebKey` set for verifying JWT tokens |Yes |Yes |No -|Represent token as Principal +|Represent token as `Principal`` |Yes |Yes |Yes -|Inject JWT as MP JWT JsonWebToken -|Yes +|Inject JWT as MP JSON Web Token (JWT) |Yes |No |Authorization Code Flow @@ -168,60 +193,79 @@ Below is a summary of the options. |Yes |No |No -|UserInfo support +|User info support |Yes |No |No -|Pem Key format support +|PEM key format support |No |Yes |No |SecretKey support |No -|In JsonWebKey format +|In JSON Web Key (JWK) format |No -|InnerSigned/Encrypted or Encrypted tokens +|Inner-signed and encrypted or encrypted tokens |Introspection -|Local Verification +|Local verification |Introspection -|Custom Token Verification +|Custom token verification |No -|With Injected JWTParser +|With injected JWT parser |No -|Accept JWT as cookie +|Accept JWT as a cookie |No |Yes |No |=== [[identity-providers]] -== Identity Providers +== Identity providers + -`IdentityProvider` converts the authentication credentials provided by `HttpAuthenticationMechanism` to `SecurityIdentity`. +The JPA `IdentityProvider` creates a `SecurityIdentity` instance, which is used during user authentication to verify and authorize access requests making your Quarkus application secure. -Some extensions such as `OIDC`, `OAuth2`, `SmallRye JWT` have the inlined `IdentityProvider` implementations which are specific to the supported authentication flow. -For example, `quarkus-oidc` uses its own `IdentityProvider` to convert a token to `SecurityIdentity`. -If you use `Basic` or `Form` HTTP-based authentication then you have to add an `IdentityProvider` which can convert a username and password to `SecurityIdentity`. +`IdentityProvider` converts the authentication credentials provided by `HttpAuthenticationMechanism` to a `SecurityIdentity` instance. -* For more information about `Basic` or `Form` HTTP-based authentication, see: -** xref:security-getting-started.adoc[JPA IdentityProvider] -** xref:security-jdbc.adoc[JDBC IdentityProvider] -** xref:security-ldap.adoc[LDAP IdentityProvider] +Some extensions, for example, `OIDC`, `OAuth2`, and `SmallRye JWT` have inline `IdentityProvider` implementations specific to the supported authentication flow. +For example, `quarkus-oidc` uses its own `IdentityProvider` to convert a token to a `SecurityIdentity` instance. -* For a a Basic Authentication configuration walk-through using JPA, see: -** xref:security-getting-started.adoc[Getting Started With Security] guide. +If you use `Basic` or `Form` HTTP-based authentication then you must add an `IdentityProvider` instance that can convert a username and password to a `SecurityIdentity` instance. -* For testing, use the xref:security-testing.adoc#configuring-user-information[User Properties IdentityProvider] section with the `IdentityProvider` with already set usernames, passwords, and roles in `application.properties`. +* For more information about `Basic` or `Form` HTTP-based authentication, see the following resources: +** xref:security-getting-started.adoc[Secure a Quarkus application with Basic authentication] +** xref:security-jdbc.adoc[Using security with JDBC] +** xref:security-ldap.adoc[Using security with an LDAP realm] -== Combining Authentication Mechanisms +== Authorization -One can combine multiple authentication mechanisms if they get the authentication credentials from the different sources. -For example, combining built-in `Basic` and `quarkus-oidc` `Bearer` authentication mechanisms is allowed, but combining `quarkus-oidc` `Bearer` and `smallrye-jwt` authentication mechanisms is not allowed because both will attempt to verify the token extracted from the HTTP `Authorization Bearer` scheme. +Quarkus also supports role-based access control (RBAC). +For more information about RBAC and other authorization options in Quarkus, see xref:security-authorization.adoc[Security authorization]. -=== Path Specific Authentication Mechanism +== Quarkus Security customization + +Quarkus Security is highly customizable. You can customize the following core security components of Quarkus: +* `HttpAuthenticationMechanism` +* `IdentityProvider` +* `SecurityidentityAugmentor` + +For more information about customizing Quarkus Security including reactive security, and how to register a security provider, see xref:security-customization.adoc[Security customization]. + +== Combining authentication mechanisms + +If the user credentials are provided by different sources, you can combine authentication mechanisms. +For example, you can combine built-in `Basic` and `quarkus-oidc` `Bearer` authentication mechanisms. + +[IMPORTANT] +==== +You cannot combine the `quarkus-oidc` `Bearer` and `smallrye-jwt` authentication mechanisms because both mechanisms attempt to verify the token extracted from the HTTP `Authorization Bearer` scheme. +==== + +=== Path-specific authentication mechanisms + +The following configuration example demonstrates how you can enforce a single selectable authentication mechanism for a given request path: -You can enforce that only a single authentication mechanism is selected for a given request path, for example: [source,properties] ---- quarkus.http.auth.permission.basic-or-bearer.paths=/service @@ -236,64 +280,56 @@ quarkus.http.auth.permission.bearer.policy=authenticated quarkus.http.auth.permission.bearer.auth-mechanism=bearer ---- -The value of the `auth-mechanism` property must match the authentication scheme supported by HttpAuthenticationMechanism such as `basic` or `bearer` or `form`, etc. - -== Proactive Authentication - -By default, Quarkus does what we call proactive authentication. This means that if an incoming request has a -credential then that request will always be authenticated (even if the target page does not require authentication). - -See xref:security-built-in-authentication.adoc#proactive-authentication[Proactive Authentication] for more information. - -== Authorization - -See xref:security-authorization.adoc[Security Authorization] for more information about Role Based Access Control and other authorization options. - -== Customization and other useful tips +Ensure that the value of the `auth-mechanism` property matches the authentication scheme supported by `HttpAuthenticationMechanism`, for example, `basic`, `bearer`, or `form`. -Quarkus Security is highly customizable. One can register custom ``HttpAuthenticationMechanism``s, ``IdentityProvider``s and ``SecurityidentityAugmentor``s. +== Proactive authentication -See xref:security-customization.adoc[Security Customization] for more information about customizing Quarkus Security and other useful tips about the reactive security, registering the security providers, etc. +By default, Quarkus does proactive authentication, which means that all incoming requests with credentials are authenticated regardless of whether the target page requires authentication. +For more information, see xref:security-built-in-authentication.adoc#proactive-authentication[Proactive authentication]. -== Secure connections with SSL +== Secure connections with SSL/TLS -See the xref:http-reference.adoc#ssl[Supporting secure connections with SSL] guide for more information. + For more information about how Quarkus supports secure connections with SSL/TLS, see the xref:http-reference.adoc#ssl[HTTP reference] information. -== Cross-Origin Resource Sharing +== Cross-origin resource sharing -If you plan to make your Quarkus application accessible to another application running on a different domain, you will need to configure CORS (Cross-Origin Resource Sharing). Please read the xref:http-reference.adoc#cors-filter[HTTP CORS documentation] for more information. +To make your Quarkus application accessible to another application running on a different domain, you need to configure cross-origin resource sharing (CORS). +For more information about the CORS filter that is provided by Quarkus, see the xref:http-reference.adoc#cors-filter[HTTP reference] information. -== Cross-Site Request Forgery Prevention +== Cross-site Request Forgery (CSRF) prevention -Quarkus Security provides a RESTEasy Reactive filter which can help protect against a https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery] attack. Please read the xref:csrf-prevention.adoc[Cross-Site Request Forgery Prevention] guide for more information. +Quarkus Security provides a RESTEasy Reactive filter that can protect your applications against a https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery] attack. +For more information, see xref:security-csrf-prevention.adoc[Cross-Site Request Forgery Prevention]. == SameSite cookies -Please see xref:http-reference.adoc#same-site-cookie[SameSite cookies] for information about adding a https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite[SameSite] cookie property to any of the cookies set by a Quarkus endpoint. +You can add a https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite[SameSite] cookie property to any of the cookies set by a Quarkus endpoint. +For more information, see xref:http-reference.adoc#same-site-cookie[SameSite cookies]. -== Testing +== Secret engines +Secrets engines are components that store, generate, or encrypt data. -See xref:security-testing.adoc[Security Testing] for more information about testing Quarkus Security. - -== Secret Engines - -=== Vault -Quarkus provides a very comprehensive HashiCorp Vault support, please see the link:{vault-guide}[Quarkus and HashiCorp Vault] documentation for more information. +Quarkus provides comprehensive HashiCorp Vault support. +For more information, see the link:{vault-guide}[Quarkus and HashiCorp Vault] documentation. == Secure serialization -When using Security along with RESTEasy Reactive and Jackson, Quarkus can limit the fields that are included in JSON serialization based on the configured security. See the xref:resteasy-reactive.adoc#secure-serialization[RESTEasy Reactive documentation] for details. +If your Quarkus Security architecture includes RESTEasy Reactive and Jackson, Quarkus can limit the fields that are included in JSON serialization based on the configured security. +For more information, see xref:resteasy-reactive.adoc#secure-serialization[Writing REST services with RESTEasy Reactive]. == National Vulnerability Database -Most of Quarkus tags have been registered in link:https://nvd.nist.gov[National Vulnerability Database] (NVD) using a Common Platform Enumeration (CPE) name format. -All registered Quarkus CPE names can be found using link:https://nvd.nist.gov/products/cpe/search/results?namingFormat=2.3&keyword=quarkus[this search query]. -If a Quarkus tag represented by the given CPE name entry is affected by some CVE then you'll be able to follow a provided link to that CVE. +Most of the Quarkus tags are registered in the US link:https://nvd.nist.gov[National Vulnerability Database] (NVD) in Common Platform Enumeration (CPE) name format. +To view the registered Quarkus CPE names, use link:https://nvd.nist.gov/products/cpe/search/results?namingFormat=2.3&keyword=quarkus[this search query]. + +If the NVE database flags a CVE against a Quarkus tag, a link that provides more details about the CVE is added to the given CPE name entry. -We will be asking the NVD CPE team to update the list as well as link Quarkus CPE name entries with the related CVEs on a regular basis. -If you work with the link:https://jeremylong.github.io/DependencyCheck/dependency-check-maven/[OWASP Dependency Check Plugin] which is using NVD feeds to detect the vulnerabilities at the application build time and see a false positive reported then please re-open link:https://github.com/quarkusio/quarkus/issues/2611[this issue] and provide the details. +The NVD CPE team updates the list regularly, but if you encounter a false positive, report the details by creating an issue in the link:https://github.com/quarkusio/quarkus/issues/2611[quarkusio] repository. -You can add `OWASP Dependency Check Plugin` to your project's `pom.xml` like this: +You can detect the vulnerabilities at the application build time with an NVD feed by using the Maven link:https://jeremylong.github.io/DependencyCheck/dependency-check-maven/[OWASP Dependency check plugin]. + + +To add the OWASP Dependency check plugin to your Quarkus Maven project, add the following XML configuration to the `pom.xml` file: [source,xml] ---- @@ -304,9 +340,12 @@ You can add `OWASP Dependency Check Plugin` to your project's `pom.xml` like thi ---- -where `owasp-dependency-check-plugin.version` should be set to `7.1.1` or later. +[IMPORTANT] +==== +Set the `owasp-dependency-check-plugin.version` value to `7.1.1` or later. +==== -You can configure the plugin like this: +Next, configure the plugin as follows: [source,xml] ---- @@ -324,18 +363,14 @@ You can configure the plugin like this: ---- -You can change `failBuildOnCVSS` value to detect less severe issues as well. - -A suppression list may vary depending on whether you'd like to keep checking the false positives to avoid missing something or not. -For example, it can look like this: - +To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress the false positives, as demonstrated in the following code sample: [source,xml] ---- @@ -389,11 +424,18 @@ For example, it can look like this: Suppress the false positive CPE for graal-sdk to GraalVM (the JVM distribution) ]]> - ^org\.graalvm\.sdk:graal-sdk:.*$ - cpe:/a:oracle:graalvm + ^org\.graalvm\.sdk:g like this ---- -Such a suppression list has to be carefully prepared and revisited from time to time. You should consider making individual suppressions time limited by adding an `until` attribute, for example: `...`. It will let you doublecheck that only the same known false positives are reported when the suppression period expires, and after reviewing the report you can set a new expiry date. +Ensure that you review and update the suppression list regularly to ensure that the results are up to date. +You can optionally apply a time limit to individual suppressions by adding an expiry attribute, as outlined in the following example: + +`...` +You can adjust the expiry date if you need to. + +== Quarkus Security testing +When testing Quarkus security, ensure that your `IdentityProvider` is already set with usernames, passwords, and roles in `application.properties`. +For more information about testing Quarkus Security, see xref:security-testing.adoc#configuring-user-information[Configuring user information]. \ No newline at end of file diff --git a/docs/src/main/asciidoc/smallrye-graphql.adoc b/docs/src/main/asciidoc/smallrye-graphql.adoc index c6f79f30585ba..832deb1dfbae2 100644 --- a/docs/src/main/asciidoc/smallrye-graphql.adoc +++ b/docs/src/main/asciidoc/smallrye-graphql.adoc @@ -742,8 +742,8 @@ Update `GalaxyService` to provide search: .collect(Collectors.toList()); results.addAll(matchingFilms); List matchingCharacters = getAllCharacters().stream() - .filter(character -> character.name.contains(query) - || character.surname.contains(query)) + .filter(character -> character.getName().contains(query) + || character.getSurname().contains(query)) .collect(Collectors.toList()); results.addAll(matchingCharacters); return results; diff --git a/docs/src/main/asciidoc/smallrye-metrics.adoc b/docs/src/main/asciidoc/smallrye-metrics.adoc index c78629b116f46..9324e141fb783 100644 --- a/docs/src/main/asciidoc/smallrye-metrics.adoc +++ b/docs/src/main/asciidoc/smallrye-metrics.adoc @@ -5,6 +5,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// = SmallRye Metrics +:extension-status: deprecated include::./attributes.adoc[] @@ -15,7 +16,14 @@ SmallRye Metrics allows applications to gather metrics and statistics that provi Apart from application-specific metrics described in this guide, you may also use built-in metrics exposed by various Quarkus extensions. These are described in the guide for each particular extension that supports built-in metrics. -IMPORTANT: xref:micrometer.adoc[Micrometer] is the recommended approach to metrics for Quarkus. Use the SmallRye Metrics extension when it is required to retain MicroProfile specification compatibility. +[IMPORTANT] +==== +xref:micrometer.adoc[Micrometer] is the recommended approach to metrics for Quarkus. Use the SmallRye Metrics extension when it is required to retain MicroProfile specification compatibility. + +When Quarkus will upgrade to Eclipse MicroProfile 6, the SmallRye Metrics support will be discontinued. +==== + +include::{includes}/extension-status.adoc[] == Prerequisites diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index e90502dc4dfe2..482f95f8cf1f5 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -23,11 +23,13 @@ import io.quarkus.agroal.runtime.DataSourceSupport; import io.quarkus.agroal.runtime.DataSources; import io.quarkus.agroal.runtime.DataSourcesJdbcBuildTimeConfig; +import io.quarkus.agroal.runtime.JdbcDriver; import io.quarkus.agroal.runtime.TransactionIntegration; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -197,7 +199,10 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, SslNativeConfigBuildItem sslNativeConfig, Capabilities capabilities, BuildProducer additionalBeans, - BuildProducer syntheticBeanBuildItemBuildProducer) { + BuildProducer syntheticBeanBuildItemBuildProducer, + BuildProducer unremovableBeans) { + additionalBeans.produce(new AdditionalBeanBuildItem(JdbcDriver.class)); + if (aggregatedBuildTimeConfigBuildItems.isEmpty()) { // No datasource has been configured so bail out return; @@ -209,8 +214,8 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, // add the @DataSource class otherwise it won't be registered as a qualifier additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(DataSource.class).build()); - // add implementations of AgroalPoolInterceptor - additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(AgroalPoolInterceptor.class)); + // make AgroalPoolInterceptor beans unremovable, users still have to make them beans + unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AgroalPoolInterceptor.class)); // create the DataSourceSupport bean that DataSourceProducer uses as a dependency DataSourceSupport dataSourceSupport = getDataSourceSupport(aggregatedBuildTimeConfigBuildItems, sslNativeConfig, diff --git a/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/AwtFeature.java b/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/AwtFeature.java index 17e709d5bdf43..34b8606751dca 100644 --- a/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/AwtFeature.java +++ b/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/AwtFeature.java @@ -1,23 +1,22 @@ package io.quarkus.awt.runtime.graal; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; /** - * Note that this initialization s not enough if user wants to deserialize actual images + * Note that this initialization is not enough if user wants to deserialize actual images * (e.g. from XML). AWT Extension must be loaded for decoding JDK supported image formats. */ public class AwtFeature implements Feature { @Override public void afterRegistration(AfterRegistrationAccess access) { - final RuntimeClassInitializationSupport runtimeInit = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); - final String reason = "Quarkus run time init for AWT"; - runtimeInit.initializeAtRunTime("com.sun.imageio", reason); - runtimeInit.initializeAtRunTime("java.awt", reason); - runtimeInit.initializeAtRunTime("javax.imageio", reason); - runtimeInit.initializeAtRunTime("sun.awt", reason); - runtimeInit.initializeAtRunTime("sun.font", reason); - runtimeInit.initializeAtRunTime("sun.java2d", reason); + // Quarkus run time init for AWT + RuntimeClassInitialization.initializeAtRunTime( + "com.sun.imageio", + "java.awt", + "javax.imageio", + "sun.awt", + "sun.font", + "sun.java2d"); } } diff --git a/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/DarwinAwtFeature.java b/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/DarwinAwtFeature.java index d17c0a8ce6a88..99f32efd5ebbd 100644 --- a/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/DarwinAwtFeature.java +++ b/extensions/awt/runtime/src/main/java/io/quarkus/awt/runtime/graal/DarwinAwtFeature.java @@ -1,10 +1,9 @@ package io.quarkus.awt.runtime.graal; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import io.quarkus.runtime.util.JavaVersionUtil; @@ -13,10 +12,8 @@ public class DarwinAwtFeature implements Feature { @Override public void afterRegistration(AfterRegistrationAccess access) { if (JavaVersionUtil.isJava17OrHigher()) { - final RuntimeClassInitializationSupport runtimeInit = ImageSingletons - .lookup(RuntimeClassInitializationSupport.class); - final String reason = "Quarkus run time init for AWT in Darwin"; - runtimeInit.initializeAtRunTime("sun.lwawt.macosx", reason); + // Quarkus run time init for AWT in Darwin + RuntimeClassInitialization.initializeAtRunTime("sun.lwawt.macosx"); } } } diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java index 16ca8f2a036c7..5d6e88167a7b2 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java @@ -54,6 +54,8 @@ public class JibConfig { * If this is set, then it will be used as the entry point of the container image. * There are a few things to be aware of when creating an entry point *

* * When this is not set, a proper default entrypoint will be constructed. @@ -77,10 +79,12 @@ public class JibConfig { * If this is set, then it will be used as the entry point of the container image. * There are a few things to be aware of when creating an entry point * * * When this is not set, a proper default entrypoint will be constructed. diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index 41a20eff3c8f6..116905e2ff1fc 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -562,6 +562,8 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag .setEnvironment(getEnvironmentVariables(jibConfig)) .setLabels(allLabels(jibConfig, containerImageConfig, containerImageLabels)); + mayInheritEntrypoint(jibContainerBuilder, entrypoint, jibConfig.jvmArguments); + if (jibConfig.useCurrentTimestamp) { jibContainerBuilder.setCreationTime(now); } @@ -596,6 +598,15 @@ public JibContainerBuilder addLayer(JibContainerBuilder jibContainerBuilder, Lis return jibContainerBuilder.addFileEntriesLayer(layerConfigurationBuilder.build()); } + private void mayInheritEntrypoint(JibContainerBuilder jibContainerBuilder, List entrypoint, + List arguments) { + if (entrypoint.size() == 1 && "INHERIT".equals(entrypoint.get(0))) { + jibContainerBuilder + .setEntrypoint((List) null) + .setProgramArguments(arguments); + } + } + private List determineEffectiveJvmArguments(JibConfig jibConfig, Optional appCDSResult) { List effectiveJvmArguments = new ArrayList<>(jibConfig.jvmArguments); jibConfig.jvmAdditionalArguments.ifPresent(effectiveJvmArguments::addAll); @@ -666,6 +677,7 @@ private JibContainerBuilder createContainerBuilderFromLegacyJar(String baseJvmIm if (jibConfig.jvmEntrypoint.isPresent()) { jibContainerBuilder.setEntrypoint(jibConfig.jvmEntrypoint.get()); + mayInheritEntrypoint(jibContainerBuilder, jibConfig.jvmEntrypoint.get(), jibConfig.jvmArguments); } return jibContainerBuilder; @@ -702,6 +714,8 @@ private JibContainerBuilder createContainerBuilderFromNative(JibConfig jibConfig .setEnvironment(getEnvironmentVariables(jibConfig)) .setLabels(allLabels(jibConfig, containerImageConfig, containerImageLabels)); + mayInheritEntrypoint(jibContainerBuilder, entrypoint, jibConfig.nativeArguments.orElse(null)); + if (jibConfig.useCurrentTimestamp) { jibContainerBuilder.setCreationTime(Instant.now()); } diff --git a/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java b/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java index 44803e7d84ca2..fae32662211ac 100644 --- a/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java +++ b/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java @@ -176,7 +176,7 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch( throw new BuildException("Dev services for Elasticsearch didn't support Opensearch", Collections.emptyList()); } - // Hibernate search Elasticsearch have a version configuration property, we need to check that it is coherent + // Hibernate Search Elasticsearch have a version configuration property, we need to check that it is coherent // with the image we are about to launch if (buildItemConfig.version != null) { String containerTag = config.imageName.substring(config.imageName.indexOf(':') + 1); @@ -197,7 +197,7 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch( // Starting the server final Supplier defaultElasticsearchSupplier = () -> { ElasticsearchContainer container = new ElasticsearchContainer( - DockerImageName.parse(config.imageName)); + DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("elasticsearch/elasticsearch")); ConfigureUtil.configureSharedNetwork(container, "elasticsearch"); if (config.serviceName != null) { container.withLabel(DEV_SERVICE_LABEL, config.serviceName); diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java index c995eb501b7c0..b73d71e2c7604 100644 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/auth/GrpcAuthTest.java @@ -68,7 +68,7 @@ void shouldSecureUniEndpoint() { client.unaryCall(Security.Container.newBuilder().setText("woo-hoo").build()) .subscribe().with(e -> resultCount.incrementAndGet()); - await().atMost(5, TimeUnit.SECONDS) + await().atMost(10, TimeUnit.SECONDS) .until(() -> resultCount.get() == 1); } @@ -82,7 +82,7 @@ void shouldSecureMultiEndpoint() { .supplier(() -> (Security.Container.newBuilder().setText("woo-hoo").build())).atMost(4)) .subscribe().with(e -> results.add(e.getIsOnEventLoop())); - await().atMost(5, TimeUnit.SECONDS) + await().atMost(10, TimeUnit.SECONDS) .until(() -> results.size() == 5); assertThat(results.stream().filter(e -> !e)).isEmpty(); @@ -101,7 +101,7 @@ void shouldFailWithInvalidCredentials() { .onFailure().invoke(error::set) .subscribe().with(e -> resultCount.incrementAndGet()); - await().atMost(5, TimeUnit.SECONDS) + await().atMost(10, TimeUnit.SECONDS) .until(() -> error.get() != null); } @@ -118,7 +118,7 @@ void shouldFailWithInvalidInsufficientRole() { .onFailure().invoke(error::set) .subscribe().with(e -> resultCount.incrementAndGet()); - await().atMost(5, TimeUnit.SECONDS) + await().atMost(10, TimeUnit.SECONDS) .until(() -> error.get() != null); } diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java new file mode 100644 index 0000000000000..7f2278a6f6493 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java @@ -0,0 +1,70 @@ +package io.quarkus.grpc.server.interceptors; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; + +import javax.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.grpc.ForwardingServerCall; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.*; +import io.quarkus.grpc.GlobalInterceptor; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.server.services.HelloService; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +public class FailingInInterceptorTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(GreeterGrpc.class.getPackage()) + .addClasses(MyFailingInterceptor.class, GreeterBean.class, HelloRequest.class, HelloService.class)); + + @GrpcClient + Greeter greeter; + + @Test + void test() { + Uni result = greeter.sayHello(HelloRequest.newBuilder().setName("ServiceA").build()); + assertThatThrownBy(() -> result.await().atMost(Duration.ofSeconds(4))) + .isInstanceOf(StatusRuntimeException.class) + .hasMessageContaining("UNKNOWN"); + } + + @ApplicationScoped + @GlobalInterceptor + public static class MyFailingInterceptor implements ServerInterceptor { + + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, + ServerCallHandler next) { + return next + .startCall(new ForwardingServerCall.SimpleForwardingServerCall(call) { + + @Override + public void sendMessage(RespT message) { + throw new IllegalArgumentException("BOOM"); + } + + @Override + public void close(Status status, Metadata trailers) { + super.close(status, trailers); + } + }, headers); + } + } + +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInterceptorTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInterceptorTest.java new file mode 100644 index 0000000000000..5fb5b090cc92c --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInterceptorTest.java @@ -0,0 +1,60 @@ +package io.quarkus.grpc.server.interceptors; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; + +import javax.enterprise.context.ApplicationScoped; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.Greeter; +import io.grpc.examples.helloworld.GreeterBean; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.quarkus.grpc.GlobalInterceptor; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.server.services.HelloService; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +public class FailingInterceptorTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(GreeterGrpc.class.getPackage()) + .addClasses(MyFailingInterceptor.class, GreeterBean.class, HelloRequest.class, HelloService.class)); + + @GrpcClient + Greeter greeter; + + @Test + void test() { + Uni result = greeter.sayHello(HelloRequest.newBuilder().setName("ServiceA").build()); + assertThatThrownBy(() -> result.await().atMost(Duration.ofSeconds(4))) + .isInstanceOf(StatusRuntimeException.class) + .hasMessageContaining("UNKNOWN"); + } + + @ApplicationScoped + @GlobalInterceptor + public static class MyFailingInterceptor implements ServerInterceptor { + + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, + ServerCallHandler next) { + throw new IllegalArgumentException("BOOM!"); + } + } + +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/GrpcContextPropagationTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/GrpcContextPropagationTest.java new file mode 100644 index 0000000000000..83278b2073d38 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/GrpcContextPropagationTest.java @@ -0,0 +1,39 @@ +package io.quarkus.grpc.server.interceptors; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.grpc.examples.helloworld.Greeter; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test reproducing #26830. + */ +public class GrpcContextPropagationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(GreeterGrpc.class.getPackage()) + .addClasses(MyFirstInterceptor.class, MyInterceptedGreeting.class)); + + @GrpcClient + Greeter greeter; + + @Test + void test() { + HelloReply foo = greeter.sayHello(HelloRequest.newBuilder().setName("foo").build()).await().indefinitely(); + assertThat(foo.getMessage()).isEqualTo("hello k1 - 1"); + foo = greeter.sayHello(HelloRequest.newBuilder().setName("foo").build()).await().indefinitely(); + assertThat(foo.getMessage()).isEqualTo("hello k1 - 2"); + } + +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyFirstInterceptor.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyFirstInterceptor.java index ea796231910ca..bee9dba9d37bc 100644 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyFirstInterceptor.java +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyFirstInterceptor.java @@ -1,8 +1,12 @@ package io.quarkus.grpc.server.interceptors; +import java.util.concurrent.atomic.AtomicInteger; + import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.spi.Prioritized; +import io.grpc.Context; +import io.grpc.Contexts; import io.grpc.ForwardingServerCall; import io.grpc.Metadata; import io.grpc.ServerCall; @@ -15,19 +19,26 @@ @GlobalInterceptor public class MyFirstInterceptor implements ServerInterceptor, Prioritized { + public static Context.Key KEY_1 = Context.key("X-TEST_1"); + public static Context.Key KEY_2 = Context.keyWithDefault("X-TEST_2", -1); private volatile long callTime; + private AtomicInteger counter = new AtomicInteger(); + @Override public ServerCall.Listener interceptCall(ServerCall serverCall, Metadata metadata, ServerCallHandler serverCallHandler) { - return serverCallHandler - .startCall(new ForwardingServerCall.SimpleForwardingServerCall(serverCall) { - @Override - public void close(Status status, Metadata trailers) { - callTime = System.nanoTime(); - super.close(status, trailers); - } - }, metadata); + + Context ctx = Context.current().withValue(KEY_1, "k1").withValue(KEY_2, counter.incrementAndGet()); + return Contexts.interceptCall(ctx, new ForwardingServerCall.SimpleForwardingServerCall<>(serverCall) { + + @Override + public void close(Status status, Metadata trailers) { + callTime = System.nanoTime(); + super.close(status, trailers); + } + }, metadata, serverCallHandler); + } public long getLastCall() { diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyInterceptedGreeting.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyInterceptedGreeting.java new file mode 100644 index 0000000000000..1929da8abd640 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/MyInterceptedGreeting.java @@ -0,0 +1,18 @@ +package io.quarkus.grpc.server.interceptors; + +import io.grpc.examples.helloworld.Greeter; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.quarkus.grpc.GrpcService; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; + +@GrpcService +public class MyInterceptedGreeting implements Greeter { + @Override + @Blocking + public Uni sayHello(HelloRequest request) { + return Uni.createFrom().item(() -> HelloReply.newBuilder() + .setMessage("hello " + MyFirstInterceptor.KEY_1.get() + " - " + MyFirstInterceptor.KEY_2.get()).build()); + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/grpc/override/ContextStorageOverride.java b/extensions/grpc/runtime/src/main/java/io/grpc/override/ContextStorageOverride.java new file mode 100644 index 0000000000000..84de72b532fdd --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/grpc/override/ContextStorageOverride.java @@ -0,0 +1,67 @@ +package io.grpc.override; + +import io.grpc.Context; +import io.smallrye.common.vertx.VertxContext; +import io.vertx.core.Vertx; + +/** + * Override gRPC context storage to rely on duplicated context when available. + */ +public class ContextStorageOverride extends Context.Storage { + + private static final ThreadLocal fallback = new ThreadLocal<>(); + + private static final String GRPC_CONTEXT = "GRPC_CONTEXT"; + + @Override + public Context doAttach(Context toAttach) { + Context current = current(); + io.vertx.core.Context dc = Vertx.currentContext(); + if (dc != null && VertxContext.isDuplicatedContext(dc)) { + dc.putLocal(GRPC_CONTEXT, toAttach); + } else { + fallback.set(toAttach); + } + return current; + } + + @Override + public void detach(Context context, Context toRestore) { + io.vertx.core.Context dc = Vertx.currentContext(); + if (toRestore != Context.ROOT) { + if (dc != null && VertxContext.isDuplicatedContext(dc)) { + dc.putLocal(GRPC_CONTEXT, toRestore); + } else { + fallback.set(toRestore); + } + } else { + if (dc != null && VertxContext.isDuplicatedContext(dc)) { + // Do nothing duplicated context are not shared. + } else { + fallback.set(null); + } + } + } + + @Override + public Context current() { + if (VertxContext.isOnDuplicatedContext()) { + Context current = Vertx.currentContext().getLocal(GRPC_CONTEXT); + if (current == null) { + return Context.ROOT; + } + return current; + } else { + Context current = fallback.get(); + if (current == null) { + return Context.ROOT; + } + return current; + } + } + + @Override + public void attach(Context toAttach) { + // do nothing, should not be called. + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java index 21d7b0ee42249..b876ab00e3001 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java @@ -2,6 +2,8 @@ import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import java.util.function.Supplier; import javax.enterprise.context.ApplicationScoped; @@ -13,9 +15,11 @@ import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import io.grpc.Status; import io.quarkus.grpc.GlobalInterceptor; import io.smallrye.common.vertx.VertxContext; import io.vertx.core.Context; +import io.vertx.core.Handler; import io.vertx.core.Vertx; @ApplicationScoped @@ -44,7 +48,7 @@ public ServerCall.Listener interceptCall(ServerCall(() -> next.startCall(call, headers), local); + return new ListenedOnDuplicatedContext<>(call, () -> next.startCall(call, headers), local); } else { log.warn("Unable to run on a duplicated context - interceptor not called on the Vert.x event loop"); return next.startCall(call, headers); @@ -56,67 +60,99 @@ public int getPriority() { return Integer.MAX_VALUE; } - static class ListenedOnDuplicatedContext extends ServerCall.Listener { + static class ListenedOnDuplicatedContext extends ServerCall.Listener { private final Context context; private final Supplier> supplier; + private final ServerCall call; private ServerCall.Listener delegate; - public ListenedOnDuplicatedContext(Supplier> supplier, Context context) { + private final AtomicBoolean closed = new AtomicBoolean(); + + public ListenedOnDuplicatedContext(ServerCall call, Supplier> supplier, + Context context) { this.context = context; this.supplier = supplier; + this.call = call; } private synchronized ServerCall.Listener getDelegate() { if (delegate == null) { - delegate = supplier.get(); + try { + delegate = supplier.get(); + } catch (Throwable t) { + // If the interceptor supplier throws an exception, catch it, and close the call. + log.warn("Unable to retrieve gRPC Server call listener", t); + close(t); + return null; + } } return delegate; } - @Override - public void onMessage(ReqT message) { + private void close(Throwable t) { + if (closed.compareAndSet(false, true)) { + call.close(Status.fromThrowable(t), new Metadata()); + } + } + + private void invoke(Consumer> invocation) { if (Vertx.currentContext() == context) { - getDelegate().onMessage(message); + ServerCall.Listener listener = getDelegate(); + if (listener == null) { + return; + } + try { + invocation.accept(listener); + } catch (Throwable t) { + close(t); + } } else { - context.runOnContext(x -> getDelegate().onMessage(message)); + context.runOnContext(new Handler() { + @Override + public void handle(Void x) { + ServerCall.Listener listener = ListenedOnDuplicatedContext.this.getDelegate(); + if (listener == null) { + return; + } + try { + invocation.accept(listener); + } catch (Throwable t) { + close(t); + } + } + }); } } + @Override + public void onMessage(ReqT message) { + invoke(new Consumer>() { + @Override + public void accept(ServerCall.Listener listener) { + listener.onMessage(message); + } + }); + } + @Override public void onReady() { - if (Vertx.currentContext() == context) { - getDelegate().onReady(); - } else { - context.runOnContext(x -> getDelegate().onReady()); - } + invoke(ServerCall.Listener::onReady); } @Override public void onHalfClose() { - if (Vertx.currentContext() == context) { - getDelegate().onHalfClose(); - } else { - context.runOnContext(x -> getDelegate().onHalfClose()); - } + invoke(ServerCall.Listener::onHalfClose); } @Override public void onCancel() { - if (Vertx.currentContext() == context) { - getDelegate().onCancel(); - } else { - context.runOnContext(x -> getDelegate().onCancel()); - } + invoke(ServerCall.Listener::onCancel); } @Override public void onComplete() { - if (Vertx.currentContext() == context) { - getDelegate().onComplete(); - } else { - context.runOnContext(x -> getDelegate().onComplete()); - } + invoke(ServerCall.Listener::onComplete); } } } diff --git a/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/HibernateEnversBuildTimeConfigPersistenceUnit.java b/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/HibernateEnversBuildTimeConfigPersistenceUnit.java index c71c07716308e..5c439cb39cc41 100644 --- a/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/HibernateEnversBuildTimeConfigPersistenceUnit.java +++ b/extensions/hibernate-envers/runtime/src/main/java/io/quarkus/hibernate/envers/HibernateEnversBuildTimeConfigPersistenceUnit.java @@ -24,7 +24,7 @@ public class HibernateEnversBuildTimeConfigPersistenceUnit { * * @asciidoclet */ - @ConfigItem(defaultValueDocumentation = "`true` if Hibernate ORM is enabled; `false` otherwise") + @ConfigItem(defaultValueDocumentation = "'true' if Hibernate ORM is enabled; 'false' otherwise") public Optional active = Optional.empty(); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java new file mode 100644 index 0000000000000..faee479dab034 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java @@ -0,0 +1,24 @@ +package io.quarkus.hibernate.orm.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; + +/** + * Activates the native-image features included in the module + * org.hibernate:hibernate-graalvm. + */ +@BuildSteps +public class GraalVMFeatures { + + @BuildStep + NativeImageFeatureBuildItem staticNativeImageFeature() { + return new NativeImageFeatureBuildItem("org.hibernate.graalvm.internal.GraalVMStaticFeature"); + } + + @BuildStep + NativeImageFeatureBuildItem queryParsingSupportFeature() { + return new NativeImageFeatureBuildItem("org.hibernate.graalvm.internal.QueryParsingSupport"); + } + +} diff --git a/extensions/hibernate-orm/pom.xml b/extensions/hibernate-orm/pom.xml index 4efc7831aedb9..eb2a3a90ace5c 100644 --- a/extensions/hibernate-orm/pom.xml +++ b/extensions/hibernate-orm/pom.xml @@ -25,7 +25,6 @@ de.thetaphi forbiddenapis - ${forbiddenapis-maven-plugin.version} verify-forbidden-apis diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java index cda963fac09ac..7ba8bb8141912 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java @@ -26,7 +26,7 @@ public class HibernateOrmRuntimeConfigPersistenceUnit { * * @asciidoclet */ - @ConfigItem(defaultValueDocumentation = "`true` if Hibernate ORM is enabled; `false` otherwise") + @ConfigItem(defaultValueDocumentation = "'true' if Hibernate ORM is enabled; 'false' otherwise") public Optional active = Optional.empty(); /** diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java index 6979394ca30cc..d91637c6cc975 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java @@ -34,7 +34,7 @@ public class HibernateSearchElasticsearchRuntimeConfigPersistenceUnit { * * @asciidoclet */ - @ConfigItem(defaultValueDocumentation = "`true` if Hibernate Search is enabled; `false` otherwise") + @ConfigItem(defaultValueDocumentation = "'true' if Hibernate Search is enabled; 'false' otherwise") public Optional active; /** diff --git a/extensions/hibernate-validator/runtime/pom.xml b/extensions/hibernate-validator/runtime/pom.xml index 2ecab2bd809c7..df18cf60093ef 100644 --- a/extensions/hibernate-validator/runtime/pom.xml +++ b/extensions/hibernate-validator/runtime/pom.xml @@ -82,6 +82,11 @@ + + io.quarkus.resteasy.reactive + resteasy-reactive + true + org.jboss.spec.javax.ws.rs jboss-jaxrs-api_2.1_spec diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveViolationExceptionMapper.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveViolationExceptionMapper.java index ec3f4912b1f69..7e55a79e80a09 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveViolationExceptionMapper.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveViolationExceptionMapper.java @@ -1,6 +1,9 @@ package io.quarkus.hibernate.validator.runtime.jaxrs; +import static io.quarkus.hibernate.validator.runtime.jaxrs.ValidatorMediaTypeUtil.SUPPORTED_MEDIA_TYPES; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -10,21 +13,18 @@ import javax.validation.ElementKind; import javax.validation.Path; import javax.validation.ValidationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; -import org.jboss.resteasy.api.validation.Validation; +import org.jboss.resteasy.reactive.server.core.CurrentRequestManager; @Provider public class ResteasyReactiveViolationExceptionMapper implements ExceptionMapper { - @Context - HttpHeaders headers; + private static final String VALIDATION_HEADER = "validation-exception"; @Override public Response toResponse(ValidationException exception) { @@ -69,10 +69,14 @@ private boolean isReturnValueViolation(ConstraintViolation violation) { private Response buildViolationReportResponse(ConstraintViolationException cve) { Status status = Status.BAD_REQUEST; Response.ResponseBuilder builder = Response.status(status); - builder.header(Validation.VALIDATION_HEADER, "true"); + builder.header(VALIDATION_HEADER, "true"); + var rrContext = CurrentRequestManager.get(); // Check standard media types. - MediaType mediaType = ValidatorMediaTypeUtil.getAcceptMediaTypeFromSupported(headers.getAcceptableMediaTypes()); + MediaType mediaType = ValidatorMediaTypeUtil.getAcceptMediaType( + rrContext.getHttpHeaders().getAcceptableMediaTypes(), + rrContext.getTarget() != null ? Arrays.asList(rrContext.getTarget().getProduces().getSortedMediaTypes()) + : SUPPORTED_MEDIA_TYPES); if (mediaType == null) { mediaType = MediaType.APPLICATION_JSON_TYPE; } diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ValidatorMediaTypeUtil.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ValidatorMediaTypeUtil.java index 8eeede84b2166..90e5ac4f82323 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ValidatorMediaTypeUtil.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ValidatorMediaTypeUtil.java @@ -10,7 +10,7 @@ */ public final class ValidatorMediaTypeUtil { - private static final List SUPPORTED_MEDIA_TYPES = Arrays.asList( + static final List SUPPORTED_MEDIA_TYPES = Arrays.asList( MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE, MediaType.TEXT_XML_TYPE, @@ -20,16 +20,6 @@ private ValidatorMediaTypeUtil() { } - /** - * Look up the right media type taking into account the HTTP request and the supported media types. - * - * @param mediaTypesFromRequest list of media types in the HTTP request. - * @return one supported media type from either the HTTP request or the annotation. - */ - public static MediaType getAcceptMediaTypeFromSupported(List mediaTypesFromRequest) { - return getAcceptMediaType(mediaTypesFromRequest, SUPPORTED_MEDIA_TYPES); - } - /** * Look up the right media type taking into account the HTTP request and the media types defined in the `@Produces` * annotation. diff --git a/extensions/jaxp/deployment/src/main/java/io/quarkus/jaxp/deployment/JaxpProcessor.java b/extensions/jaxp/deployment/src/main/java/io/quarkus/jaxp/deployment/JaxpProcessor.java index 152b93015fef6..883b1e8ac0c3b 100644 --- a/extensions/jaxp/deployment/src/main/java/io/quarkus/jaxp/deployment/JaxpProcessor.java +++ b/extensions/jaxp/deployment/src/main/java/io/quarkus/jaxp/deployment/JaxpProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.jaxp.deployment; +import java.util.function.Consumer; import java.util.stream.Stream; import io.quarkus.deployment.annotations.BuildProducer; @@ -30,6 +31,8 @@ void reflectiveClasses(BuildProducer reflectiveClass) @BuildStep void resourceBundles(BuildProducer resourceBundle) { + Consumer resourceBundleItemProducer = bundleName -> resourceBundle + .produce(new NativeImageResourceBundleBuildItem(bundleName, "java.xml")); Stream.of( "com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages", "com.sun.org.apache.xml.internal.res.XMLErrorResources", @@ -37,8 +40,7 @@ void resourceBundles(BuildProducer resourceB "com.sun.org.apache.xerces.internal.impl.msg.XMLMessages", "com.sun.org.apache.xerces.internal.impl.msg.XMLSchemaMessages", "com.sun.org.apache.xerces.internal.impl.xpath.regex.message") - .map(NativeImageResourceBundleBuildItem::new) - .forEach(resourceBundle::produce); + .forEach(resourceBundleItemProducer); } @BuildStep diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java index 319ff7d521d45..03b704e56fe5e 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java @@ -152,7 +152,7 @@ public void createTopicPartitions(String bootstrapServers, KafkaDevServiceCfg co // get current partitions for topics asked to be created Set currentTopics = adminClient.listTopics().names() .get(adminClientTimeout, TimeUnit.MILLISECONDS); - Map partitions = adminClient.describeTopics(currentTopics).all() + Map partitions = adminClient.describeTopics(currentTopics).allTopicNames() .get(adminClientTimeout, TimeUnit.MILLISECONDS); // find new topics to create List newTopics = topicPartitions.entrySet().stream() @@ -246,7 +246,7 @@ private RunningDevService startKafka(DockerStatusBuildItem dockerStatusBuildItem KAFKA_BOOTSTRAP_SERVERS, container.getBootstrapServers()); } else { RedPandaKafkaContainer container = new RedPandaKafkaContainer( - DockerImageName.parse(config.imageName), + DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("vectorized/redpanda"), config.fixedExposedPort, launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT ? config.serviceName : null, useSharedNetwork, config.redpanda); diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java index c9b75dc1d00c0..d9901b7bb12b5 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java @@ -30,8 +30,13 @@ public class KafkaAdminClient { @PostConstruct void init() { - Map conf = new HashMap<>(config); + Map conf = new HashMap<>(); conf.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, DEFAULT_ADMIN_CLIENT_TIMEOUT); + for (Map.Entry entry : config.entrySet()) { + if (AdminClientConfig.configNames().contains(entry.getKey())) { + conf.put(entry.getKey(), entry.getValue().toString()); + } + } client = AdminClient.create(conf); } @@ -81,4 +86,11 @@ public Collection getAclInfo() throws InterruptedException, Executio var options = new DescribeAclsOptions().timeoutMs(1_000); return client.describeAcls(filter, options).values().get(); } + + public Map describeTopics(Collection topicNames) + throws InterruptedException, ExecutionException { + return client.describeTopics(topicNames) + .allTopicNames() + .get(); + } } diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaTopicClient.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaTopicClient.java index 174ef04aa08b8..8a2340fd475de 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaTopicClient.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/ui/KafkaTopicClient.java @@ -9,12 +9,9 @@ import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.clients.admin.AdminClientConfig; import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -27,6 +24,7 @@ import org.apache.kafka.common.serialization.BytesSerializer; import org.apache.kafka.common.utils.Bytes; +import io.quarkus.kafka.client.runtime.KafkaAdminClient; import io.quarkus.kafka.client.runtime.ui.model.Order; import io.quarkus.kafka.client.runtime.ui.model.converter.KafkaModelConverter; import io.quarkus.kafka.client.runtime.ui.model.request.KafkaMessageCreateRequest; @@ -38,8 +36,8 @@ public class KafkaTopicClient { // TODO: make configurable private static final int RETRIES = 3; - //TODO: inject me - private AdminClient adminClient; + @Inject + KafkaAdminClient adminClient; KafkaModelConverter modelConverter = new KafkaModelConverter(); @@ -47,13 +45,6 @@ public class KafkaTopicClient { @Identifier("default-kafka-broker") Map config; - @PostConstruct - void init() { - Map conf = new HashMap<>(config); - conf.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "5000"); - adminClient = AdminClient.create(conf); - } - private Producer createProducer() { Map config = new HashMap<>(this.config); @@ -260,8 +251,6 @@ var record = new ProducerRecord<>(request.getTopic(), request.getPartition(), By public List partitions(String topicName) throws ExecutionException, InterruptedException { return adminClient.describeTopics(List.of(topicName)) - .allTopicNames() - .get() .values().stream() .reduce((a, b) -> { throw new IllegalStateException( diff --git a/extensions/keycloak-admin-client-reactive/runtime/pom.xml b/extensions/keycloak-admin-client-reactive/runtime/pom.xml index 81a08005fa838..dc194217932f8 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/pom.xml +++ b/extensions/keycloak-admin-client-reactive/runtime/pom.xml @@ -73,6 +73,14 @@ io.quarkus quarkus-extension-maven-plugin + + + + org.keycloak:keycloak-admin-client + org/keycloak/admin/client/JacksonProvider.class + + + maven-compiler-plugin diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index 608572fc702ee..5d5f7626e2b36 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -13,6 +13,7 @@ import org.jboss.logmanager.Level; import io.netty.channel.EventLoopGroup; +import io.netty.resolver.dns.DnsServerAddressStreamProviders; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.logging.InternalLoggerFactory; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -253,11 +254,23 @@ RuntimeInitializedClassBuildItem runtimeInitBcryptUtil() { //if debug logging is enabled netty logs lots of exceptions //see https://github.com/quarkusio/quarkus/issues/5213 @BuildStep - LogCleanupFilterBuildItem cleanup() { + LogCleanupFilterBuildItem cleanupUnsafeLog() { return new LogCleanupFilterBuildItem(PlatformDependent.class.getName() + "0", Level.TRACE, "direct buffer constructor", "jdk.internal.misc.Unsafe", "sun.misc.Unsafe"); } + /** + * On mac, if you do not have the `MacOSDnsServerAddressStreamProvider` class, Netty prints a warning saying it + * falls back to the default system DNS provider. This is not a problem and generates tons of questions. + * + * @return the log cleanup item removing the message + */ + @BuildStep + LogCleanupFilterBuildItem cleanupMacDNSInLog() { + return new LogCleanupFilterBuildItem(DnsServerAddressStreamProviders.class.getName(), Level.WARN, + "Can not find io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider in the classpath"); + } + private String calculateMaxOrder(OptionalInt userConfig, List minMaxOrderBuildItems, boolean shouldWarn) { int result = DEFAULT_NETTY_ALLOCATOR_MAX_ORDER; diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 5a11fad2a485c..4fe2d13be6e9e 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -410,6 +410,8 @@ public QuarkusOidcContainer(DockerImageName dockerImageName, OptionalInt fixedEx this.fixedExposedPort = fixedExposedPort; this.startCommand = startCommand; this.showLogs = showLogs; + + super.setWaitStrategy(Wait.forLogMessage(".*Keycloak.*started.*", 1)); } @Override @@ -482,7 +484,6 @@ protected void configure() { } LOG.infof("Using %s powered Keycloak distribution", keycloakX ? "Quarkus" : "WildFly"); - super.setWaitStrategy(Wait.forLogMessage(".*Keycloak.*started.*", 1)); } private Integer findRandomPort() { @@ -546,7 +547,7 @@ private FileTime getRealmFileLastModifiedDate(Optional realm) { private void createDefaultRealm(String keycloakUrl, Map users, String oidcClientId, String oidcClientSecret) { - RealmRepresentation realm = createRealmRep(); + RealmRepresentation realm = createDefaultRealmRep(); realm.getClients().add(createClient(oidcClientId, oidcClientSecret)); for (Map.Entry entry : users.entrySet()) { @@ -637,13 +638,15 @@ private List getUserRoles(String user) { : roles; } - private RealmRepresentation createRealmRep() { + private RealmRepresentation createDefaultRealmRep() { RealmRepresentation realm = new RealmRepresentation(); realm.setRealm(getDefaultRealmName()); realm.setEnabled(true); realm.setUsers(new ArrayList<>()); realm.setClients(new ArrayList<>()); + realm.setAccessTokenLifespan(600); + realm.setSsoSessionMaxLifespan(600); RolesRepresentation roles = new RolesRepresentation(); List realmRoles = new ArrayList<>(); diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html index acafa6886780e..b73635a79de3f 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html +++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html @@ -117,7 +117,7 @@ } function testServiceWithAccessToken(){ - var servicePath = $('#servicePath').val(); + var servicePath = getServicePath(); $.post("testServiceWithToken", { serviceUrl: "http://localhost:" + port + servicePath, @@ -129,7 +129,7 @@ } function testServiceWithIdToken(){ - var servicePath = $('#servicePath').val(); + var servicePath = getServicePath(); $.post("testServiceWithToken", { serviceUrl: "http://localhost:" + port + servicePath, @@ -253,9 +253,23 @@ return token; } } + + function printLoginError(){ + var search = window.location.search; + var errorDescription = search.match(/error_description=([^&]+)/)[1]; + $('#errorDescription').append(""); + $('#errorDescription').append("" + "Login error: " + decodeURI(errorDescription).replaceAll("+", " ") + ""); + } + {/if} {/if} +{#if info:oidcApplicationType is 'web-app'} +function signInToService(servicePath) { + window.open("http://localhost:" + port + servicePath); +} +{/if} + {#if info:oidcGrantType is 'password'} function testServiceWithPassword(userName, password, servicePath){ @@ -394,15 +408,13 @@ $('#results').append("
"); } -function printLoginError(){ - var search = window.location.search; - var errorDescription = search.match(/error_description=([^&]+)/)[1]; - $('#errorDescription').append(""); - $('#errorDescription').append("" + "Login error: " + decodeURI(errorDescription).replaceAll("+", " ") + ""); +function getServicePath() { + var servicePath = $('#servicePath').val(); + return servicePath.startsWith("/") ? servicePath : ("/" + servicePath); } -function signInToService(servicePath) { - window.open("http://localhost:" + port + servicePath); +function clearResults() { + $('#results').text(''); } {/script} @@ -562,9 +574,12 @@
Decoded

-
+
+ + +
@@ -600,7 +615,7 @@
Decoded
- +
{#if info:swaggerIsAvailable??} @@ -620,6 +635,9 @@
Decoded
+ + +
{#else if info:oidcGrantType is 'client_credentials'} @@ -638,7 +656,7 @@
Decoded
- +
{#if info:swaggerIsAvailable??} @@ -658,6 +676,9 @@
Decoded
+ + +
{/if} @@ -674,7 +695,7 @@
Decoded
- +
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 8eee36c73e17f..84576f9348c5f 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -130,6 +130,7 @@ private Uni processRedirectFromOidc(RoutingContext context, Oi OidcUtils.removeCookie(context, oidcTenantConfig, stateCookie.getName()); if (!isStateValid(requestParams, parsedStateCookieValue[0])) { + LOG.error("State verification has failed, completing the code flow with HTTP status 401"); return Uni.createFrom().failure(new AuthenticationCompletionException()); } @@ -164,10 +165,12 @@ public Uni apply(TenantConfigContext tenantContext) { LOG.debugf("Error URI: %s", finalErrorUri); return Uni.createFrom().failure(new AuthenticationRedirectException(finalErrorUri)); } else { + LOG.error( + "Authentication has failed but no error handler is found, completing the code flow with HTTP status 401"); return Uni.createFrom().failure(new AuthenticationCompletionException()); } } else { - LOG.debug("State cookie is present but neither 'code' nor 'error' query parameter is returned"); + LOG.error("State cookie is present but neither 'code' nor 'error' query parameter is returned"); return Uni.createFrom().failure(new AuthenticationCompletionException()); } @@ -241,7 +244,7 @@ public Uni apply(SecurityIdentity identity) { public Uni apply(Throwable t) { if (t instanceof AuthenticationRedirectException) { LOG.debug("Redirecting after the reauthentication"); - throw (AuthenticationRedirectException) t; + return Uni.createFrom().failure((AuthenticationRedirectException) t); } if (!(t instanceof TokenAutoRefreshException)) { @@ -250,17 +253,20 @@ public Uni apply(Throwable t) { .hasErrorCode(ErrorCodes.EXPIRED); if (!expired) { - LOG.debugf("Authentication failure: %s", t.getCause()); - throw new AuthenticationCompletionException(t.getCause()); + LOG.errorf("ID token verification failure: %s", t.getCause()); + return Uni.createFrom() + .failure(new AuthenticationCompletionException(t.getCause())); } if (session.getRefreshToken() == null) { LOG.debug( "Token has expired, token refresh is not possible because the refresh token is null"); - throw new AuthenticationFailedException(t.getCause()); + return Uni.createFrom() + .failure(new AuthenticationFailedException(t.getCause())); } if (!configContext.oidcConfig.token.refreshExpired) { LOG.debug("Token has expired, token refresh is not allowed"); - throw new AuthenticationFailedException(t.getCause()); + return Uni.createFrom() + .failure(new AuthenticationFailedException(t.getCause())); } LOG.debug("Token has expired, trying to refresh it"); return refreshSecurityIdentity(configContext, @@ -495,7 +501,8 @@ private PkceStateBean createPkceStateBean(TenantConfigContext configContext) { String codeChallenge = encoder.encodeToString(codeChallengeBytes); bean.setCodeChallenge(codeChallenge); } catch (Exception ex) { - throw new AuthenticationFailedException(ex); + LOG.errorf("Code challenge creation failure: %s", ex.getMessage()); + throw new AuthenticationCompletionException(ex); } return bean; @@ -541,15 +548,15 @@ private Uni performCodeFlow(IdentityProviderManager identityPr public Uni apply(final AuthorizationCodeTokens tokens, final Throwable tOuter) { if (tOuter != null) { - LOG.debugf("Exception during the code to token exchange: %s", tOuter.getMessage()); + LOG.errorf("Exception during the code to token exchange: %s", tOuter.getMessage()); return Uni.createFrom().failure(new AuthenticationCompletionException(tOuter)); } boolean internalIdToken = !configContext.oidcConfig.authentication.isIdTokenRequired().orElse(true); if (tokens.getIdToken() == null) { if (!internalIdToken) { - return Uni.createFrom() - .failure(new AuthenticationCompletionException("ID Token is not available")); + LOG.errorf("ID token is not available in the authorization code grant response"); + return Uni.createFrom().failure(new AuthenticationCompletionException()); } else { tokens.setIdToken(generateInternalIdToken(configContext.oidcConfig, null)); } @@ -616,7 +623,7 @@ public Throwable apply(Throwable tInner) { LOG.debugf("Starting the final redirect"); return tInner; } - LOG.debugf("ID token verification has failed: %s", tInner.getMessage()); + LOG.errorf("ID token verification has failed: %s", tInner.getMessage()); return new AuthenticationCompletionException(tInner); } }); @@ -638,9 +645,9 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta try { json = OidcUtils.decryptJson(parsedStateCookieValue[1], configContext.getPkceSecretKey()); } catch (Exception ex) { - LOG.tracef("State cookie value can not be decrypted for the %s tenant", + LOG.errorf("State cookie value can not be decrypted for the %s tenant", configContext.oidcConfig.tenantId.get()); - throw new AuthenticationFailedException(ex); + throw new AuthenticationCompletionException(ex); } bean.setRestorePath(json.getString(STATE_COOKIE_RESTORE_PATH)); bean.setCodeVerifier(json.getString(OidcConstants.PKCE_CODE_VERIFIER)); @@ -672,7 +679,7 @@ public Uni apply(Void t) { JsonObject idTokenJson = OidcUtils.decodeJwtContent(idToken); if (!idTokenJson.containsKey("exp") || !idTokenJson.containsKey("iat")) { - LOG.debug("ID Token is required to contain 'exp' and 'iat' claims"); + LOG.error("ID Token is required to contain 'exp' and 'iat' claims"); throw new AuthenticationCompletionException(); } long maxAge = idTokenJson.getLong("exp") - idTokenJson.getLong("iat"); @@ -779,7 +786,8 @@ private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue try { return OidcUtils.encryptJson(json, configContext.getPkceSecretKey()); } catch (Exception ex) { - throw new AuthenticationFailedException(ex); + LOG.errorf("State containing the code verifier can not be encrypted: %s", ex.getMessage()); + throw new AuthenticationCompletionException(ex); } } else { return extraStateValue.getRestorePath(); @@ -857,7 +865,7 @@ private Uni refreshSecurityIdentity(TenantConfigContext config @Override public Uni apply(final AuthorizationCodeTokens tokens, final Throwable t) { if (t != null) { - LOG.errorf("ID token refresh has failed: %s", t.getMessage()); + LOG.debugf("ID token refresh has failed: %s", t.getMessage()); if (autoRefresh) { LOG.debug("Using the current SecurityIdentity since the ID token is still valid"); return Uni.createFrom().item(((TokenAutoRefreshException) t).getSecurityIdentity()); @@ -967,7 +975,9 @@ private Uni buildLogoutRedirectUriUni(RoutingContext context, TenantConfig .map(new Function() { @Override public Void apply(Void t) { - throw new AuthenticationRedirectException(buildLogoutRedirectUri(configContext, idToken, context)); + String logoutUri = buildLogoutRedirectUri(configContext, idToken, context); + LOG.debugf("Logout uri: %s"); + throw new AuthenticationRedirectException(logoutUri); } }); } diff --git a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index da5eafa9b7cd4..537ba5c392865 100644 --- a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -454,7 +454,7 @@ void registerProviders(BuildProducer reflectiveClass, List ignoreClientProviderBuildItems, CombinedIndexBuildItem combinedIndexBuildItem, ResteasyInjectionReadyBuildItem injectorFactory, - RestClientRecorder restClientRecorder) { + RestClientRecorder restClientRecorder, Capabilities capabilities) { for (IgnoreClientProviderBuildItem item : ignoreClientProviderBuildItems) { jaxrsProvidersToRegisterBuildItem.getProviders().remove(item.getProviderClassName()); @@ -465,6 +465,12 @@ void registerProviders(BuildProducer reflectiveClass, jaxrsProvidersToRegisterBuildItem.useBuiltIn(), jaxrsProvidersToRegisterBuildItem.getProviders(), jaxrsProvidersToRegisterBuildItem.getContributedProviders()); + if (!capabilities.isPresent(Capability.RESTEASY)) { + // ResteasyProviderFactory will use our implementation when accessing instance statically. That's not + // necessary when RESTEasy classic is present as then provider factory with correct provider classes is generated. + restClientRecorder.setResteasyProviderFactoryInstance(); + } + // register the providers for reflection for (String providerToRegister : jaxrsProvidersToRegisterBuildItem.getProviders()) { reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, providerToRegister)); diff --git a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientRecorder.java b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientRecorder.java index 8cf6d8be030b4..da136486a8cad 100644 --- a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientRecorder.java +++ b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientRecorder.java @@ -46,6 +46,10 @@ public InjectorFactory getInjectorFactory() { providerFactory = clientProviderFactory; } + public void setResteasyProviderFactoryInstance() { + ResteasyProviderFactory.setInstance(providerFactory); + } + private static void registerProviders(ResteasyProviderFactory providerFactory, boolean useBuiltIn, Set providersToRegister, Set contributedProviders) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java index 16b6355c47190..76fdcbbb826d4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java @@ -15,6 +15,9 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig; import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler; +import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner; +import org.jboss.resteasy.reactive.common.processor.scanning.ScannedSerializer; +import org.jboss.resteasy.reactive.common.processor.scanning.SerializerScanningResult; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; import org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer; @@ -43,6 +46,7 @@ public class QuarkusServerEndpointIndexer private final ResteasyReactiveRecorder resteasyReactiveRecorder; private final Predicate applicationClassPredicate; + private SerializerScanningResult serializerScanningResult; QuarkusServerEndpointIndexer(Builder builder) { super(builder); @@ -170,10 +174,33 @@ private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, M return; } if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson())) { - LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#" - + info.name() - + "' but no JSON extension has been added. Consider adding 'quarkus-resteasy-reactive-jackson' or 'quarkus-resteasy-reactive-jsonb'."); + if (serializerScanningResult == null) { + serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult); + } + boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(serializerScanningResult.getReaders()); + boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(serializerScanningResult.getWriters()); + if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) { + LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#" + + info.name() + + "' but no JSON extension has been added. Consider adding 'quarkus-resteasy-reactive-jackson' or 'quarkus-resteasy-reactive-jsonb'."); + } + } + } + + private boolean appProvidedJsonProviderExists(List providers) { + boolean appProvidedJsonReaderExists = false; + for (ScannedSerializer provider : providers) { + for (String mt : provider.getMediaTypeStrings()) { + if (isJson(mt)) { + appProvidedJsonReaderExists = true; + break; + } + } + if (appProvidedJsonReaderExists) { + break; + } } + return appProvidedJsonReaderExists; } private boolean isDefaultJson() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java index f57a1d437d771..32c92031137db 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java @@ -4,6 +4,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.net.URI; +import java.net.URISyntaxException; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.UriBuilder; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -34,6 +38,19 @@ void shouldDetermineUrlViaStork() { assertThat(greeting).isEqualTo("hello, black and white bird"); } + @Test + void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException { + String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request().get(String.class); + assertThat(greeting).isEqualTo("Hello"); + + greeting = ClientBuilder.newClient().target(new URI("stork://hello-service/hello")).request().get(String.class); + assertThat(greeting).isEqualTo("Hello"); + + greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/hello")).request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello"); + } + @Test void shouldDetermineUrlViaStorkCDI() { String greeting = client.echo("big bird"); diff --git a/extensions/schema-registry/devservice/deployment/src/main/java/io/quarkus/apicurio/registry/devservice/DevServicesApicurioRegistryProcessor.java b/extensions/schema-registry/devservice/deployment/src/main/java/io/quarkus/apicurio/registry/devservice/DevServicesApicurioRegistryProcessor.java index 79e659a5562e5..7698a26f35537 100644 --- a/extensions/schema-registry/devservice/deployment/src/main/java/io/quarkus/apicurio/registry/devservice/DevServicesApicurioRegistryProcessor.java +++ b/extensions/schema-registry/devservice/deployment/src/main/java/io/quarkus/apicurio/registry/devservice/DevServicesApicurioRegistryProcessor.java @@ -173,7 +173,8 @@ private RunningDevService startApicurioRegistry(DockerStatusBuildItem dockerStat getRegistryUrlConfigs("http://" + address.getUrl()))) .orElseGet(() -> { ApicurioRegistryContainer container = new ApicurioRegistryContainer( - DockerImageName.parse(config.imageName), config.fixedExposedPort, + DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("apicurio/apicurio-registry-mem"), + config.fixedExposedPort, launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT ? config.serviceName : null, useSharedNetwork); timeout.ifPresent(container::withStartupTimeout); diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java index 2bb65a37f5d50..2cae3cd455bb6 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java @@ -34,7 +34,12 @@ protected void doHandle(final RoutingContext ctx) { if (event.succeeded()) { ServerWebSocket serverWebSocket = event.result(); String subprotocol = serverWebSocket.subProtocol(); - GraphQLWebsocketHandler handler = null; + if (subprotocol == null) { + log.warn("Websocket subprotocol is null"); + serverWebSocket.close(); + return; + } + GraphQLWebsocketHandler handler; switch (subprotocol) { case "graphql-transport-ws": handler = new GraphQLTransportWSSubprotocolHandler( @@ -49,14 +54,14 @@ protected void doHandle(final RoutingContext ctx) { serverWebSocket.close(); return; } - log.debug("Starting websocket with subprotocol = " + subprotocol); + log.debugf("Starting websocket with subprotocol = %s", subprotocol); GraphQLWebsocketHandler finalHandler = handler; serverWebSocket.closeHandler(v -> finalHandler.onClose()); serverWebSocket.endHandler(v -> finalHandler.onEnd()); - serverWebSocket.exceptionHandler(t -> finalHandler.onThrowable(t)); - serverWebSocket.textMessageHandler(m -> finalHandler.onMessage(m)); + serverWebSocket.exceptionHandler(finalHandler::onThrowable); + serverWebSocket.textMessageHandler(finalHandler::onMessage); } else { - log.warn("WebSocket failed", event.cause()); + log.warn("Websocket failed", event.cause()); } }); } else { diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java index a6d3e00dbb110..923f053356b54 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; import org.eclipse.microprofile.graphql.GraphQLException; @@ -108,10 +109,19 @@ private CompletionStage> invokeBatchBlocking(DataFetchingEnvironment dfe return (List) operationInvoker.invokePrivileged(arguments); }); + // this gets called on a batch error, so that error callbacks can run with the proper context too + Consumer onErrorConsumer = threadContext.contextualConsumer((Throwable exception) -> { + eventEmitter.fireOnDataFetchError(dfe.getExecutionId().toString(), exception); + }); + // Here call blocking with context BlockingHelper.runBlocking(vc, contextualCallable, result); - return result.future().toCompletionStage(); - + return result.future().toCompletionStage() + .whenComplete((resultList, error) -> { + if (error != null) { + onErrorConsumer.accept(error); + } + }); } private boolean runBlocking(DataFetchingEnvironment dfe) { diff --git a/extensions/smallrye-metrics/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/smallrye-metrics/runtime/src/main/resources/META-INF/quarkus-extension.yaml index ef89ea41073fa..c0f47046841be 100644 --- a/extensions/smallrye-metrics/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/smallrye-metrics/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -12,7 +12,7 @@ metadata: guide: "https://quarkus.io/guides/microprofile-metrics" categories: - "observability" - status: "stable" + status: "deprecated" config: - "quarkus.smallrye-metrics." - "mp.metrics." diff --git a/extensions/smallrye-opentracing/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/smallrye-opentracing/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 8837f47db11c9..ef5bb82541cfe 100644 --- a/extensions/smallrye-opentracing/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/smallrye-opentracing/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,7 +11,7 @@ metadata: guide: "https://quarkus.io/guides/opentracing" categories: - "observability" - status: "stable" + status: "deprecated" config: - "quarkus.jaeger." - "mp.opentracing." diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java index d6c6fa0146adb..99fdf9a906bf9 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/amqp/deployment/AmqpDevServicesProcessor.java @@ -176,7 +176,7 @@ private RunningDevService startAmqpBroker(DockerStatusBuildItem dockerStatusBuil final Supplier defaultAmqpBrokerSupplier = () -> { // Starting the broker ArtemisContainer container = new ArtemisContainer( - DockerImageName.parse(config.imageName), + DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("artemiscloud/activemq-artemis-broker"), config.extra, config.fixedExposedPort, launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT ? config.serviceName : null); diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application-secured.properties b/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application-secured.properties index 93bedede04be4..8f2da7b189603 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application-secured.properties +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application-secured.properties @@ -3,6 +3,7 @@ amqp-username=artemis amqp-password=artemis mp.messaging.outgoing.source.connector=smallrye-amqp +mp.messaging.outgoing.source.durable=true mp.messaging.incoming.in.connector=smallrye-amqp mp.messaging.incoming.in.address=source diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application.properties b/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application.properties index 5506833771e38..f6e591ab27f7b 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application.properties +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/src/test/resources/application.properties @@ -1,6 +1,7 @@ amqp-port=5672 mp.messaging.outgoing.source.connector=smallrye-amqp +mp.messaging.outgoing.source.durable=true mp.messaging.incoming.in.connector=smallrye-amqp mp.messaging.incoming.in.address=source diff --git a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java index 85763f737ae33..39d102ff57582 100644 --- a/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java +++ b/extensions/smallrye-reactive-messaging-rabbitmq/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/rabbitmq/deployment/RabbitMQDevServicesProcessor.java @@ -176,7 +176,7 @@ private RunningDevService startRabbitMQBroker(DockerStatusBuildItem dockerStatus } ConfiguredRabbitMQContainer container = new ConfiguredRabbitMQContainer( - DockerImageName.parse(config.imageName), + DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("rabbitmq"), config.fixedExposedPort, config.fixedExposedHttpPort, launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT ? config.serviceName : null); diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/disable-http.conf b/extensions/vertx-http/deployment/src/test/resources/conf/disable-http.conf index f60ba91d3b3aa..580909ea73575 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/disable-http.conf +++ b/extensions/vertx-http/deployment/src/test/resources/conf/disable-http.conf @@ -1,4 +1,4 @@ # Enable SSL, configure the key store quarkus.http.insecure-requests=REDIRECT -quarkus.http.ssl.certificate.file=server-cert.pem -quarkus.http.ssl.certificate.key-file=server-key.pem +quarkus.http.ssl.certificate.files=server-cert.pem +quarkus.http.ssl.certificate.key-files=server-key.pem diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java index 9e56af5b332b7..10717a6f0c869 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java @@ -190,7 +190,7 @@ public void handle(Void event) { synchronized (connection) { eof = true; if (waiting) { - connection.notify(); + connection.notifyAll(); } } } @@ -212,7 +212,7 @@ public void handle(Throwable event) { } } if (waiting) { - connection.notify(); + connection.notifyAll(); } } } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index 1d4ff48c2413d..05d88e50dbd35 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -305,10 +305,15 @@ private static VertxOptions convertToVertxOptions(VertxConfiguration conf, Vertx initializeClusterOptions(conf, options); } + FileSystemOptions fileSystemOptions = new FileSystemOptions() + .setFileCachingEnabled(conf.caching) + .setClassPathResolvingEnabled(conf.classpathResolving); + String fileCacheDir = System.getProperty(CACHE_DIR_BASE_PROP_NAME); if (fileCacheDir == null) { File tmp = new File(System.getProperty("java.io.tmpdir", ".") + File.separator + VERTX_CACHE); - if (!tmp.isDirectory()) { + boolean cacheDirRequired = conf.caching || conf.classpathResolving; + if (!tmp.isDirectory() && cacheDirRequired) { if (!tmp.mkdirs()) { LOGGER.warnf("Unable to create Vert.x cache directory : %s", tmp.getAbsolutePath()); } @@ -322,26 +327,25 @@ private static VertxOptions convertToVertxOptions(VertxConfiguration conf, Vertx } } - File cache = getRandomDirectory(tmp); - LOGGER.debugf("Vert.x Cache configured to: %s", cache.getAbsolutePath()); - fileCacheDir = cache.getAbsolutePath(); - if (shutdown != null) { - shutdown.addLastShutdownTask(new Runnable() { - @Override - public void run() { - // Recursively delete the created directory and all the files - deleteDirectory(cache); - // We do not delete the vertx-cache directory on purpose, as it could be used concurrently by - // another application. In the worse case, it's just an empty directory. - } - }); + if (cacheDirRequired) { + File cache = getRandomDirectory(tmp); + LOGGER.debugf("Vert.x Cache configured to: %s", cache.getAbsolutePath()); + fileSystemOptions.setFileCacheDir(cache.getAbsolutePath()); + if (shutdown != null) { + shutdown.addLastShutdownTask(new Runnable() { + @Override + public void run() { + // Recursively delete the created directory and all the files + deleteDirectory(cache); + // We do not delete the vertx-cache directory on purpose, as it could be used concurrently by + // another application. In the worse case, it's just an empty directory. + } + }); + } } } - options.setFileSystemOptions(new FileSystemOptions() - .setFileCachingEnabled(conf.caching) - .setFileCacheDir(fileCacheDir) - .setClassPathResolvingEnabled(conf.classpathResolving)); + options.setFileSystemOptions(fileSystemOptions); options.setWorkerPoolSize(conf.workerPoolSize); options.setInternalBlockingPoolSize(conf.internalBlockingPoolSize); blockingThreadPoolSize = conf.internalBlockingPoolSize; diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java index b5174a1f87ff3..0a7313ca0473b 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java @@ -6,22 +6,21 @@ import java.util.concurrent.ConcurrentMap; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509KeyManager; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import io.netty.handler.ssl.ApplicationProtocolConfig; -import io.netty.handler.ssl.OpenSslServerContext; +import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.Vertx; -import io.vertx.core.VertxException; import io.vertx.core.dns.AddressResolverOptions; import io.vertx.core.eventbus.EventBusOptions; import io.vertx.core.eventbus.impl.HandlerHolder; @@ -140,83 +139,57 @@ EventBusOptions options() { } } -@TargetClass(className = "io.vertx.core.net.impl.SSLHelper") -final class Target_io_vertx_core_net_impl_SSLHelper { - - @Alias - private boolean client; +@TargetClass(className = "io.vertx.core.spi.tls.DefaultSslContextFactory") +final class Target_DefaultSslContextFactory { @Alias private Set enabledCipherSuites; - @Alias - private boolean openSsl; - @Alias private List applicationProtocols; @Alias - private KeyManagerFactory getKeyMgrFactory(VertxInternal vertx) throws Exception { - return null; - } + private ClientAuth clientAuth; @Substitute - private SslContext createContext(VertxInternal vertx, boolean useAlpn, X509KeyManager mgr, - TrustManagerFactory trustMgrFactory) { - try { - SslContextBuilder builder; - if (client) { - builder = SslContextBuilder.forClient(); - KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx); - if (keyMgrFactory != null) { - builder.keyManager(keyMgrFactory); - } - } else { - if (mgr != null) { - builder = SslContextBuilder.forServer(mgr.getPrivateKey(null), null, mgr.getCertificateChain(null)); - } else { - KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx); - if (keyMgrFactory == null) { - throw new VertxException("Key/certificate is mandatory for SSL"); - } - builder = SslContextBuilder.forServer(keyMgrFactory); - } - } - Collection cipherSuites = enabledCipherSuites; - if (openSsl) { - throw new UnsupportedOperationException("OpenSSL not supported in native images"); - } else { - builder.sslProvider(SslProvider.JDK); - if (cipherSuites == null || cipherSuites.isEmpty()) { - cipherSuites = Target_io_vertx_core_net_impl_DefaultJDKCipherSuite.get(); - } - } - if (trustMgrFactory != null) { - builder.trustManager(trustMgrFactory); + private SslContext createContext(boolean useAlpn, boolean client, KeyManagerFactory kmf, TrustManagerFactory tmf) + throws SSLException { + SslContextBuilder builder; + if (client) { + builder = SslContextBuilder.forClient(); + if (kmf != null) { + builder.keyManager(kmf); } - if (cipherSuites != null && cipherSuites.size() > 0) { - builder.ciphers(cipherSuites); - } - if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) { - builder.applicationProtocolConfig(new ApplicationProtocolConfig( - ApplicationProtocolConfig.Protocol.ALPN, - ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, - ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - applicationProtocols)); - } - SslContext ctx = builder.build(); - if (ctx instanceof OpenSslServerContext) { - throw new UnsupportedOperationException("OpenSSL not supported in native images"); - } - return ctx; - } catch (Exception e) { - throw new VertxException(e); + } else { + builder = SslContextBuilder.forServer(kmf); + } + Collection cipherSuites = enabledCipherSuites; + builder.sslProvider(SslProvider.JDK); + if (cipherSuites == null || cipherSuites.isEmpty()) { + cipherSuites = Target_io_vertx_core_spi_tls_DefaultJDKCipherSuite.get(); + } + if (tmf != null) { + builder.trustManager(tmf); + } + if (cipherSuites != null && cipherSuites.size() > 0) { + builder.ciphers(cipherSuites); + } + if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) { + builder.applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + applicationProtocols)); + } + if (clientAuth != null) { + builder.clientAuth(clientAuth); } + return builder.build(); } } -@TargetClass(className = "io.vertx.core.net.impl.DefaultJDKCipherSuite") -final class Target_io_vertx_core_net_impl_DefaultJDKCipherSuite { +@TargetClass(className = "io.vertx.core.spi.tls.DefaultJDKCipherSuite") +final class Target_io_vertx_core_spi_tls_DefaultJDKCipherSuite { @Alias static List get() { return null; diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 72b65cc970ce3..3935b5567745e 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -43,7 +43,7 @@ 2.0.2 1.3.3 2.4.3.Final - 5.9.0 + 5.9.1 3.8.6 3.23.1 3.5.0.Final diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java index 0ebb9780c6f83..c964fc94642af 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java @@ -18,7 +18,7 @@ * asynchronously, possibly on a different thread. *

* Note that context data and method parameters are mutable and are not guarded/synchronized. We expect them to be modified - * before or after dispatch. If modified before and after dispatch an unpredicatble behavior may occur. + * before or after dispatch. If modified before and after dispatch an unpredictable behavior may occur. */ class AroundInvokeInvocationContext extends AbstractInvocationContext { diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java index 001b08b33e944..3757ed11b36e3 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java @@ -65,6 +65,7 @@ public class QuarkusBootstrap implements Serializable { private final Properties buildSystemProperties; private final String baseName; + private final String originalBaseName; private final Path targetDirectory; private final Mode mode; @@ -102,6 +103,7 @@ private QuarkusBootstrap(Builder builder) { this.test = builder.test; this.localProjectDiscovery = builder.localProjectDiscovery; this.baseName = builder.baseName; + this.originalBaseName = builder.originalJarName; this.baseClassLoader = builder.baseClassLoader; this.targetDirectory = builder.targetDirectory; this.appModelResolver = builder.appModelResolver; @@ -235,6 +237,10 @@ public String getBaseName() { return baseName; } + public String getOriginalBaseName() { + return originalBaseName; + } + public ClassLoader getBaseClassLoader() { return baseClassLoader; } @@ -262,6 +268,7 @@ public List getClassLoaderEventListeners() { public Builder clonedBuilder() { Builder builder = new Builder() .setBaseName(baseName) + .setOriginalBaseName(originalBaseName) .setProjectRoot(projectRoot) .setBaseClassLoader(baseClassLoader) .setBuildSystemProperties(buildSystemProperties) @@ -304,6 +311,7 @@ public static class Builder { boolean rebuild; PathCollection applicationRoot; String baseName; + String originalJarName; Path projectRoot; ClassLoader baseClassLoader = ClassLoader.getSystemClassLoader(); final List additionalApplicationArchives = new ArrayList<>(); @@ -417,6 +425,11 @@ public Builder setBaseName(String baseName) { return this; } + public Builder setOriginalBaseName(String originalJarName) { + this.originalJarName = originalJarName; + return this; + } + public Builder setBaseClassLoader(ClassLoader baseClassLoader) { this.baseClassLoader = baseClassLoader; return this; diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 6b4ccaec51479..e34ae94641a12 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -42,7 +42,7 @@ 3.23.1 0.9.5 3.5.0.Final - 5.9.0 + 5.9.1 3.8.6 0.3.5 3.6.0 diff --git a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/DevModeMediator.java b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/DevModeMediator.java index 0cc428bf191f1..87d382db3c60a 100644 --- a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/DevModeMediator.java +++ b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/DevModeMediator.java @@ -9,9 +9,11 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Deque; import java.util.List; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.LinkedBlockingDeque; import org.jboss.logging.Logger; @@ -24,6 +26,8 @@ public class DevModeMediator { protected static final Logger LOGGER = Logger.getLogger(DevModeMediator.class); + public static final Deque> removedFiles = new LinkedBlockingDeque<>(); + static void doDevMode(Path appRoot) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Path deploymentClassPath = appRoot.resolve(QuarkusEntryPoint.LIB_DEPLOYMENT_DEPLOYMENT_CLASS_PATH_DAT); @@ -88,6 +92,13 @@ public void run() { closeable.close(); } closeable = null; + final List pathsToDelete = removedFiles.pollFirst(); + if (pathsToDelete != null) { + for (Path p : pathsToDelete) { + LOGGER.info("Deleting " + p); + Files.deleteIfExists(p); + } + } try { closeable = doStart(appRoot, deploymentClassPath); } catch (Exception e) { diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 850223560ca13..7ac9c49a56f05 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -36,7 +36,7 @@ 3.0.0-M7 1.6.8 2.13.4 - 5.9.0 + 5.9.1 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 4194241dfbfbb..d8bc2cefc25eb 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -40,7 +40,7 @@ 11 11 11 - 5.9.0 + 5.9.1 3.23.1 2.4.3.Final 1.1.0.Final diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java index 01d7062bc8162..f3b2e27bfc172 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java @@ -69,7 +69,7 @@ public MaybeRestClientInterface createClientProxy(ClassInfo classInfo, } clazz.setPath(path); } - List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), + List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), new HashSet<>(), clazz.getPathParameters(), clazz.getPath(), false); clazz.getMethods().addAll(methods); @@ -101,7 +101,8 @@ protected void handleClientSubResource(ResourceMethod resourceMethod, MethodInfo throw new IllegalStateException("Subresource method returns an invalid type: " + method.returnType().name()); } - List endpoints = createEndpoints(subResourceClass, subResourceClass, new HashSet<>(), new HashSet<>(), + List endpoints = createEndpoints(subResourceClass, subResourceClass, + new HashSet<>(), new HashSet<>(), new HashSet<>(), "", false); resourceMethod.setSubResourceMethods(endpoints); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java index d0cf2b2261fbd..a7ea3759d8a86 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java @@ -463,11 +463,21 @@ public long setPeriodic(long l, Handler handler) { return getDelegate().setPeriodic(l, handler); } + @Override + public long setPeriodic(long initialDelay, long delay, Handler handler) { + return getDelegate().setPeriodic(initialDelay, delay, handler); + } + @Override public TimeoutStream periodicStream(long l) { return getDelegate().periodicStream(l); } + @Override + public TimeoutStream periodicStream(long initialDelay, long delay) { + return getDelegate().periodicStream(initialDelay, delay); + } + @Override public boolean cancelTimer(long l) { return getDelegate().cancelTimer(l); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java index c81f86cb0fb87..020d0d42f6fcd 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java @@ -37,8 +37,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { try { serviceInstance = Stork.getInstance() .getService(serviceName) - .selectInstanceAndRecordStart(measureTime) - .log(); + .selectInstanceAndRecordStart(measureTime); } catch (Throwable e) { log.error("Error selecting service instance for serviceName: " + serviceName, e); requestContext.resume(e); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java index 56fc7d2f4d0fc..1b4b0355ae6e1 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java @@ -320,10 +320,45 @@ private void abortIfClosed() { protected InvocationBuilderImpl createQuarkusRestInvocationBuilder(HttpClient client, UriBuilder uri, ConfigurationImpl configuration) { - return new InvocationBuilderImpl(uri.build(), restClient, client, this, configuration, + URI actualUri = uri.build(); + registerStorkFilterIfNeeded(configuration, actualUri); + return new InvocationBuilderImpl(actualUri, restClient, client, this, configuration, handlerChain.setPreClientSendHandler(preClientSendHandler), requestContext); } + /** + * If the URI starts with stork:// or storks://, then register the StorkClientRequestFilter automatically. + * + * @param configuration the configuration + * @param actualUri the uri + */ + private static void registerStorkFilterIfNeeded(ConfigurationImpl configuration, URI actualUri) { + if (actualUri.getScheme() != null && actualUri.getScheme().startsWith("stork") + && !isStorkAlreadyRegistered(configuration)) { + configuration.register(StorkClientRequestFilter.class); + } + } + + /** + * Checks if the Stork request filter is already registered. + * We cannot use configuration.isRegistered, as the user registration uses a subclass, and so fail the equality + * expectation. + *

+ * This method prevents having the stork filter registered twice: once because the uri starts with stork:// and, + * once from the user. + * + * @param configuration the configuration + * @return {@code true} if stork is already registered. + */ + private static boolean isStorkAlreadyRegistered(ConfigurationImpl configuration) { + for (Class clazz : configuration.getClasses()) { + if (clazz.getName().startsWith(StorkClientRequestFilter.class.getName())) { + return true; + } + } + return false; + } + @Override public WebTargetImpl property(String name, Object value) { abortIfClosed(); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java index eaf1af8765fbf..5e8ffaa6d4d1f 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/QuarkusMultipartFormUpload.java @@ -20,7 +20,6 @@ import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.impl.headers.HeadersAdaptor; -import io.vertx.core.streams.Pipe; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.impl.InboundBuffer; @@ -233,15 +232,4 @@ public synchronized QuarkusMultipartFormUpload endHandler(Handler handler) return this; } - /** - * The reason we need a custom Pipe here is that if we don't manually throttle the read stream, - * the fact that the underlying connection is shared can lead to exhaustion of heap memory. - * See this for more details. - */ - @Override - public Pipe pipe() { - pause(); - return new RequestTrackingPipe<>(this, 16); - } - } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/RequestTrackingPipe.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/RequestTrackingPipe.java deleted file mode 100644 index 6b6b3f21d892e..0000000000000 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/multipart/RequestTrackingPipe.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.jboss.resteasy.reactive.client.impl.multipart; - -import java.util.concurrent.atomic.AtomicLong; - -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; -import io.vertx.core.VertxException; -import io.vertx.core.streams.Pipe; -import io.vertx.core.streams.ReadStream; -import io.vertx.core.streams.WriteStream; - -/** - * Based on {@link io.vertx.core.streams.impl.PipeImpl}. - * We can't use the aforementioned class as it is not open enough for us to add the read tracking we want. - */ -class RequestTrackingPipe implements Pipe { - - private final long maxInFlightReads; - - private final Promise result; - private final ReadStream src; - private boolean endOnSuccess = true; - private boolean endOnFailure = true; - private WriteStream dst; - - private boolean manuallyPaused = false; - - private final AtomicLong inFlightReads = new AtomicLong(0); - - public RequestTrackingPipe(ReadStream src, long maxInFlightReads) { - this.src = src; - this.maxInFlightReads = maxInFlightReads; - this.result = Promise.promise(); - - // Set handlers now - src.endHandler(result::tryComplete); - src.exceptionHandler(result::tryFail); - } - - @Override - public synchronized Pipe endOnFailure(boolean end) { - endOnFailure = end; - return this; - } - - @Override - public synchronized Pipe endOnSuccess(boolean end) { - endOnSuccess = end; - return this; - } - - @Override - public synchronized Pipe endOnComplete(boolean end) { - endOnSuccess = end; - endOnFailure = end; - return this; - } - - private void handleWriteResult(AsyncResult ack) { - long currentInFlightReads = inFlightReads.decrementAndGet(); - if (currentInFlightReads <= (maxInFlightReads / 2)) { - synchronized (RequestTrackingPipe.this) { - if (manuallyPaused) { - manuallyPaused = false; - src.resume(); - } - } - } - if (ack.failed()) { - result.tryFail(new WriteException(ack.cause())); - } - } - - @Override - public void to(WriteStream ws, Handler> completionHandler) { - if (ws == null) { - throw new NullPointerException(); - } - boolean endOnSuccess; - boolean endOnFailure; - synchronized (RequestTrackingPipe.this) { - if (dst != null) { - throw new IllegalStateException(); - } - dst = ws; - endOnSuccess = this.endOnSuccess; - endOnFailure = this.endOnFailure; - } - Handler drainHandler = v -> src.resume(); - src.handler(item -> { - ws.write(item, this::handleWriteResult); - long currentInFlightReads = inFlightReads.incrementAndGet(); - if (ws.writeQueueFull()) { - src.pause(); - ws.drainHandler(drainHandler); - } else if (currentInFlightReads > maxInFlightReads) { - synchronized (RequestTrackingPipe.this) { - src.pause(); - manuallyPaused = true; - } - } - }); - src.resume(); - result.future().onComplete(ar -> { - try { - src.handler(null); - } catch (Exception ignore) { - } - try { - src.exceptionHandler(null); - } catch (Exception ignore) { - } - try { - src.endHandler(null); - } catch (Exception ignore) { - } - if (ar.succeeded()) { - handleSuccess(completionHandler); - } else { - Throwable err = ar.cause(); - if (err instanceof WriteException) { - src.resume(); - err = err.getCause(); - } - handleFailure(err, completionHandler); - } - }); - } - - private void handleSuccess(Handler> completionHandler) { - if (endOnSuccess) { - dst.end(completionHandler); - } else { - completionHandler.handle(Future.succeededFuture()); - } - } - - private void handleFailure(Throwable cause, Handler> completionHandler) { - Future res = Future.failedFuture(cause); - if (endOnFailure) { - dst.end(ignore -> { - completionHandler.handle(res); - }); - } else { - completionHandler.handle(res); - } - } - - public void close() { - synchronized (this) { - src.exceptionHandler(null); - src.handler(null); - if (dst != null) { - dst.drainHandler(null); - dst.exceptionHandler(null); - } - } - VertxException err = new VertxException("Pipe closed", true); - if (result.tryFail(err)) { - src.resume(); - } - } - - private static class WriteException extends VertxException { - private WriteException(Throwable cause) { - super(cause, true); - } - } - -} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 41ee64f4ea399..4ee14ffe8f5b1 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -148,7 +148,7 @@ public abstract class EndpointIndexer SUPPORT_TEMPORAL_PARAMS = Set.of(INSTANT, LOCAL_DATE, LOCAL_TIME, LOCAL_DATE_TIME, + protected static final Set SUPPORT_TEMPORAL_PARAMS = Set.of(INSTANT, LOCAL_DATE, LOCAL_TIME, LOCAL_DATE_TIME, OFFSET_TIME, OFFSET_DATE_TIME, ZONED_DATE_TIME); @@ -226,7 +226,7 @@ public abstract class EndpointIndexer> factoryCreator; private final Consumer resourceMethodCallback; private final AnnotationStore annotationStore; - private final ApplicationScanningResult applicationScanningResult; + protected final ApplicationScanningResult applicationScanningResult; private final Set contextTypes; private final MultipartReturnTypeIndexerExtension multipartReturnTypeIndexerExtension; private final MultipartParameterIndexerExtension multipartParameterIndexerExtension; @@ -283,7 +283,7 @@ public Optional createEndpoints(ClassInfo classInfo, boolean cons if (classLevelExceptionMappers != null) { clazz.setClassLevelExceptionMappers(classLevelExceptionMappers); } - List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), + List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), new HashSet<>(), clazz.getPathParameters(), clazz.getPath(), considerApplication); clazz.getMethods().addAll(methods); @@ -351,7 +351,7 @@ protected abstract METHOD createResourceMethod(MethodInfo info, ClassInfo actual Map methodContext); protected List createEndpoints(ClassInfo currentClassInfo, - ClassInfo actualEndpointInfo, Set seenMethods, + ClassInfo actualEndpointInfo, Set seenMethods, Set existingClassNameBindings, Set pathParameters, String resourceClassPath, boolean considerApplication) { if (considerApplication && applicationScanningResult != null && !applicationScanningResult.keepClass(actualEndpointInfo.name().toString())) { @@ -373,6 +373,9 @@ protected List createEndpoints(ClassInfo currentClassInfo, classConsumes, pathParameters, classStreamElementType); Set classNameBindings = NameBindingUtil.nameBindingNames(index, currentClassInfo); + if (classNameBindings.isEmpty()) { + classNameBindings = existingClassNameBindings; + } for (DotName httpMethod : httpAnnotationToMethod.keySet()) { List methods = currentClassInfo.methods(); @@ -438,7 +441,7 @@ protected List createEndpoints(ClassInfo currentClassInfo, if (superClassName != null && !superClassName.equals(OBJECT)) { ClassInfo superClass = index.getClassByName(superClassName); if (superClass != null) { - ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, + ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, classNameBindings, pathParameters, resourceClassPath, considerApplication)); } } @@ -446,7 +449,7 @@ protected List createEndpoints(ClassInfo currentClassInfo, for (DotName i : interfaces) { ClassInfo superClass = index.getClassByName(i); if (superClass != null) { - ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, + ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, classNameBindings, pathParameters, resourceClassPath, considerApplication)); } } @@ -1283,8 +1286,8 @@ && isContextType(paramType.asClassType())) { currentClassInfo, actualEndpointInfo, index); } - handleOptionalParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, - genericElementType); + handleOptionalParam(existingConverters, anns, errorLocation, hasRuntimeConverters, builder, elementType, + genericElementType, currentMethodInfo); } builder.setOptional(true); } else if (convertible) { @@ -1382,8 +1385,11 @@ protected void handleSortedSetParam(Map existingConverters, Stri boolean hasRuntimeConverters, PARAM builder, String elementType) { } - protected void handleOptionalParam(Map existingConverters, String errorLocation, - boolean hasRuntimeConverters, PARAM builder, String elementType, String genericElementType) { + protected void handleOptionalParam(Map existingConverters, + Map parameterAnnotations, + String errorLocation, + boolean hasRuntimeConverters, PARAM builder, String elementType, String genericElementType, + MethodInfo currentMethodInfo) { } protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java index fa40292c38698..d906cdd2cdef7 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java @@ -17,6 +17,7 @@ import java.util.stream.Stream; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassType; @@ -35,6 +36,7 @@ public final class JandexUtil { public final static DotName DOTNAME_OBJECT = DotName.createSimple(Object.class.getName()); + public final static DotName DOTNAME_RECORD = DotName.createSimple("java.lang.Record"); private JandexUtil() { } @@ -143,7 +145,7 @@ private static List findParametersRecursively(Type type, DotName target, } // always end at Object - if (DOTNAME_OBJECT.equals(name)) { + if (DOTNAME_OBJECT.equals(name) || DOTNAME_RECORD.equals(name)) { return null; } @@ -303,25 +305,32 @@ private static ClassInfo fetchFromIndex(DotName dotName, IndexView index) { } /** - * Returns the enclosing class of the given annotation instance. For field or method annotations this - * will return the enclosing class. For parameters, this will return the enclosing class of the enclosing - * method. For classes it will return the class itself. + * Returns the enclosing class of the given annotation instance. For field, method or record component annotations, + * this will return the enclosing class. For parameters, this will return the enclosing class of the enclosing + * method. For classes, it will return the class itself. For type annotations, it will return the class enclosing + * the annotated type usage. * - * @param annotationInstance the annotation whose enclosing class to look up. - * @return the enclosing class. + * @param annotationInstance the annotation whose enclosing class to look up + * @return the enclosing class */ public static ClassInfo getEnclosingClass(AnnotationInstance annotationInstance) { - switch (annotationInstance.target().kind()) { + return getEnclosingClass(annotationInstance.target()); + } + + private static ClassInfo getEnclosingClass(AnnotationTarget annotationTarget) { + switch (annotationTarget.kind()) { case FIELD: - return annotationInstance.target().asField().declaringClass(); + return annotationTarget.asField().declaringClass(); case METHOD: - return annotationInstance.target().asMethod().declaringClass(); + return annotationTarget.asMethod().declaringClass(); case METHOD_PARAMETER: - return annotationInstance.target().asMethodParameter().method().declaringClass(); + return annotationTarget.asMethodParameter().method().declaringClass(); + case RECORD_COMPONENT: + return annotationTarget.asRecordComponent().declaringClass(); case CLASS: - return annotationInstance.target().asClass(); + return annotationTarget.asClass(); case TYPE: - return annotationInstance.target().asType().asClass(); // TODO is it legal here or should I throw ? + return getEnclosingClass(annotationTarget.asType().enclosingTarget()); default: throw new RuntimeException(); // this should not occur } @@ -338,7 +347,7 @@ public static ClassInfo getEnclosingClass(AnnotationInstance annotationInstance) * @throws RuntimeException if one of the superclasses is not indexed. */ public static boolean isSubclassOf(IndexView index, ClassInfo info, DotName parentName) { - if (info.superName().equals(DOTNAME_OBJECT)) { + if (info.superName().equals(DOTNAME_OBJECT) || info.superName().equals(DOTNAME_RECORD)) { return false; } if (info.superName().equals(parentName)) { diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index afae29c3a0045..b64a423e7572f 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -43,7 +43,7 @@ 2.0.2 2.4.3.Final 1.12.12 - 5.9.0 + 5.9.1 3.8.6 3.23.1 3.5.0.Final @@ -57,7 +57,7 @@ 1.1.6 1.7.0 1.13.1 - 4.3.2 + 4.3.4 4.5.1 1.0.0.Final 2.0.0.Final diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index d73ba9d913056..b64b2b9ddab57 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -335,8 +335,11 @@ protected void handleSortedSetParam(Map existingConverters, Stri builder.setConverter(new SortedSetConverter.SortedSetSupplier(converter)); } - protected void handleOptionalParam(Map existingConverters, String errorLocation, - boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType, String genericElementType) { + protected void handleOptionalParam(Map existingConverters, + Map parameterAnnotations, + String errorLocation, + boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType, String genericElementType, + MethodInfo currentMethodInfo) { ParameterConverterSupplier converter = null; if (genericElementType != null) { @@ -352,6 +355,9 @@ protected void handleOptionalParam(Map existingConverters, Strin converter = new SortedSetConverter.SortedSetSupplier(genericTypeConverter); builder.setSingle(false); } + } else if (SUPPORT_TEMPORAL_PARAMS.contains(DotName.createSimple(elementType))) { + converter = determineTemporalConverter(DotName.createSimple(elementType), parameterAnnotations, + currentMethodInfo); } if (converter == null) { @@ -395,6 +401,11 @@ protected String handleTrailingSlash(String path) { protected void handleTemporalParam(ServerIndexedParameter builder, DotName paramType, Map parameterAnnotations, MethodInfo currentMethodInfo) { + builder.setConverter(determineTemporalConverter(paramType, parameterAnnotations, currentMethodInfo)); + } + + private ParameterConverterSupplier determineTemporalConverter(DotName paramType, + Map parameterAnnotations, MethodInfo currentMethodInfo) { String format = null; String dateTimeFormatterProviderClassName = null; @@ -418,8 +429,7 @@ protected void handleTemporalParam(ServerIndexedParameter builder, DotName param "'java.time.Instant' types must not be annotated with '@DateFormat'", currentMethodInfo)); } - builder.setConverter(new InstantParamConverter.Supplier()); - return; + return new InstantParamConverter.Supplier(); } if ((format != null) && (dateTimeFormatterProviderClassName != null)) { @@ -432,23 +442,17 @@ protected void handleTemporalParam(ServerIndexedParameter builder, DotName param } if (LOCAL_DATE.equals(paramType)) { - builder.setConverter(new LocalDateParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new LocalDateParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } else if (LOCAL_DATE_TIME.equals(paramType)) { - builder.setConverter(new LocalDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new LocalDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } else if (LOCAL_TIME.equals(paramType)) { - builder.setConverter(new LocalTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new LocalTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } else if (OFFSET_DATE_TIME.equals(paramType)) { - builder.setConverter(new OffsetDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new OffsetDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } else if (OFFSET_TIME.equals(paramType)) { - builder.setConverter(new OffsetTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new OffsetTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } else if (ZONED_DATE_TIME.equals(paramType)) { - builder.setConverter(new ZonedDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName)); - return; + return new ZonedDateTimeParamConverter.Supplier(format, dateTimeFormatterProviderClassName); } throw new RuntimeException( diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxInputStream.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxInputStream.java index c30afb92de7ba..9c56278499d75 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxInputStream.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxInputStream.java @@ -190,7 +190,7 @@ public void handle(Void event) { synchronized (connection) { eof = true; if (waiting) { - connection.notify(); + connection.notifyAll(); } } } @@ -212,7 +212,7 @@ public void handle(Throwable event) { } } if (waiting) { - connection.notify(); + connection.notifyAll(); } } } diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java index 89a0d5eba0be9..3e79191243710 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Optional; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -33,6 +34,15 @@ public void localDateTimeAsQueryParam() { .then().statusCode(200).body(Matchers.equalTo("hello#1984")); } + @Test + public void localDateTimeAsOptionalQueryParam() { + RestAssured.get("/hello/optional?date=1984-08-08T01:02:03") + .then().statusCode(200).body(Matchers.equalTo("hello#1984")); + + RestAssured.get("/hello/optional") + .then().statusCode(200).body(Matchers.equalTo("hello#2022")); + } + @Test public void localDateTimeAsPathParam() { RestAssured.get("/hello/1995-09-21 01:02:03") @@ -53,6 +63,12 @@ public String helloQuery(@RestQuery LocalDateTime date) { return "hello#" + date.getYear(); } + @Path("optional") + @GET + public String helloOptionalQuery(@RestQuery Optional date) { + return "hello#" + date.orElse(LocalDateTime.of(2022, 1, 1, 0, 0)).getYear(); + } + @GET @Path("{date}") public String helloPath(@RestPath @DateFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime date) { diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NameBindingWithInterfaceTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NameBindingWithInterfaceTest.java new file mode 100644 index 0000000000000..10661e08b36d2 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NameBindingWithInterfaceTest.java @@ -0,0 +1,104 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import static io.restassured.RestAssured.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.io.IOException; +import java.lang.annotation.Retention; + +import javax.ws.rs.GET; +import javax.ws.rs.NameBinding; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.Provider; + +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.smallrye.mutiny.Uni; + +public class NameBindingWithInterfaceTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(BlockingHelloResource.class, ReactiveHelloResource.class, BlockingHelloApi.class, + ReactiveHelloApi.class, AddTestHeaderContainerRequestFilter.class)); + + @Test + public void blockingHello() { + get("/blocking-hello") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello")) + .header("test", "some-value"); + } + + @Test + public void reactiveHello() { + get("/reactive-hello") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello")) + .header("test", "some-value"); + } + + @SomeFilter + public static class BlockingHelloResource implements BlockingHelloApi { + + @Override + public String sayHello() { + return "hello"; + } + } + + @SomeFilter + public static class ReactiveHelloResource implements ReactiveHelloApi { + + @Override + public Uni sayHello() { + return Uni.createFrom().item("hello"); + } + } + + @Path("blocking-hello") + public interface BlockingHelloApi { + + @GET + @Produces(MediaType.TEXT_PLAIN) + String sayHello(); + } + + @Path("reactive-hello") + public interface ReactiveHelloApi { + + @GET + @Produces(MediaType.TEXT_PLAIN) + Uni sayHello(); + } + + @NameBinding + @Retention(RUNTIME) + @interface SomeFilter { + } + + @Provider + @SomeFilter + public static class AddTestHeaderContainerRequestFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().putSingle("test", "some-value"); + + } + } +} diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 239581cf0360a..ec81155d7ba80 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -51,7 +51,7 @@ 3.23.1 2.13.4 2.0.2 - 5.9.0 + 5.9.1 1.21 3.5.0.Final 3.8.6 diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/invoker.properties b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/invoker.properties new file mode 100644 index 0000000000000..5a1e6fd59b49c --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/invoker.properties @@ -0,0 +1 @@ +invoker.goals=clean package -Dquarkus.container-image.build=true diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/pom.xml b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/pom.xml new file mode 100644 index 0000000000000..e3e1993602bb0 --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + org.acme + container-build-jib-inherit + 0.1-SNAPSHOT + + UTF-8 + 3.0.0-M7 + 11 + UTF-8 + 11 + + + + + io.quarkus + quarkus-bom + @project.version@ + pom + import + + + + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-container-image-jib + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + @project.version@ + + + + build + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + native + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/java/org/acme/Hello.java b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/java/org/acme/Hello.java new file mode 100644 index 0000000000000..ad80766a17747 --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/java/org/acme/Hello.java @@ -0,0 +1,16 @@ +package org.acme; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class Hello { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } +} \ No newline at end of file diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/resources/application.properties b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/resources/application.properties new file mode 100644 index 0000000000000..22994823ba77b --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/src/main/resources/application.properties @@ -0,0 +1,8 @@ +# Configuration file +# key = value +quarkus.package.type=fast-jar +quarkus.jib.jvm-arguments=java,-Djava.util.logging.manager=org.jboss.logmanager.LogManager,-jar,quarkus-run.jar +quarkus.jib.jvm-entrypoint=INHERIT +quarkus.jib.native-arguments=./application +quarkus.jib.native-entrypoint=INHERIT +quarkus.jib.working-directory=/app diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/verify.groovy b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/verify.groovy new file mode 100644 index 0000000000000..9997de49e3f8c --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-inherit/verify.groovy @@ -0,0 +1,37 @@ +import io.quarkus.deployment.util.ExecUtil + +import java.util.concurrent.ThreadLocalRandom + +try { + ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'") +} catch (Exception ignored) { + return +} + +String image = "${System.getProperty("user.name")}/container-build-jib-inherit:0.1-SNAPSHOT" +assert ExecUtil.exec("docker", "images", image) + +String containerName = "container-build-jib-inherit-" + ThreadLocalRandom.current().nextInt(10000) +int maxTimesToCheck = 10 +int i = 0 +int hostPort = 12345 +assert ExecUtil.exec("docker", "run", "-d", "-p", "$hostPort:8080", "--name", containerName, image) + +while (true) { + try { + def response = "http://localhost:$hostPort/hello".toURL().text + assert response == "hello" + break + } catch (IOException e) { + try { + Thread.sleep(2000) + } catch (InterruptedException ignored) { + } + if ((i++) >= maxTimesToCheck) { + throw new RuntimeException("Unable to determine if container is running", e) + } + } +} +assert ExecUtil.exec("docker", "stop", containerName) +assert ExecUtil.exec("docker", "rm", containerName) +assert ExecUtil.exec("docker", "rmi", image) diff --git a/integration-tests/elytron-undertow/src/main/resources/application.properties b/integration-tests/elytron-undertow/src/main/resources/application.properties index 5ea2086daa428..6ef33186e3884 100644 --- a/integration-tests/elytron-undertow/src/main/resources/application.properties +++ b/integration-tests/elytron-undertow/src/main/resources/application.properties @@ -9,8 +9,8 @@ quarkus.security.users.embedded.users.poul=poul quarkus.security.users.embedded.roles.poul=interns quarkus.security.users.embedded.plain-text=true quarkus.servlet.context-path=/foo -quarkus.http.ssl.certificate.file=target/server-cert.pem -quarkus.http.ssl.certificate.key-file=target/server-key.pem +quarkus.http.ssl.certificate.files=target/server-cert.pem +quarkus.http.ssl.certificate.key-files=target/server-key.pem # Test that server starts with this option # See https://github.com/quarkusio/quarkus/issues/8336 quarkus.http.insecure-requests=disabled \ No newline at end of file diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/TextResource.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/TextResource.java new file mode 100644 index 0000000000000..9d7a173286183 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/TextResource.java @@ -0,0 +1,27 @@ +package io.quarkus.it.hibernate.validator; + +import javax.validation.constraints.Digits; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("text") +public class TextResource { + + @GET + @Path("/validate/{id}") + public String validate( + @Digits(integer = 5, fraction = 0, message = "numeric value out of bounds") @PathParam("id") String id) { + return id; + } + + @GET + @Path("/validate/text/{id}") + @Produces(MediaType.TEXT_PLAIN) + public String validateText( + @Digits(integer = 5, fraction = 0, message = "numeric value out of bounds") @PathParam("id") String id) { + return id; + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/TextResourceTest.java b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/TextResourceTest.java new file mode 100644 index 0000000000000..b7fc426b18367 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/TextResourceTest.java @@ -0,0 +1,28 @@ +package io.quarkus.it.hibernate.validator; + +import static io.restassured.RestAssured.when; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +public class TextResourceTest { + + @Test + public void fetchDefault() { + when().get("/text/validate/boom") + .then() + .statusCode(400) + .contentType(ContentType.TEXT); + } + + @Test + public void fetchText() { + when().get("/text/validate/boom") + .then() + .statusCode(400) + .contentType(ContentType.TEXT); + } +} diff --git a/integration-tests/kafka-avro-apicurio2/src/main/resources/application.properties b/integration-tests/kafka-avro-apicurio2/src/main/resources/application.properties index d4907bbe9f11b..a64c88f99975e 100644 --- a/integration-tests/kafka-avro-apicurio2/src/main/resources/application.properties +++ b/integration-tests/kafka-avro-apicurio2/src/main/resources/application.properties @@ -4,3 +4,5 @@ quarkus.log.category.\"org.apache.zookeeper\".level=WARN # enable health check quarkus.kafka.health.enabled=true + +quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.2.5.Final diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java index 0f9806b0f26b3..86e576f03b12a 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java @@ -51,15 +51,32 @@ public void testExtensionRemovedResources() throws Exception { assertThat(result.getProcess().waitFor()).isEqualTo(0); } + @Test + public void testExtensionTestWithNoMain() throws Exception { + testDir = initProject("projects/extension-test-with-no-main"); + running = new RunningInvoker(testDir, false); + final MavenProcessInvocationResult result = running.execute(List.of("verify"), Map.of()); + assertThat(result.getProcess().waitFor()).isEqualTo(0); + } + @Test public void testUberJarMavenPluginConfiguration() throws MavenInvocationException, IOException, InterruptedException { testDir = initProject("projects/uberjar-maven-plugin-config"); running = new RunningInvoker(testDir, false); - final MavenProcessInvocationResult result = running.execute(Collections.singletonList("package"), - Collections.emptyMap()); + final MavenProcessInvocationResult result = running.execute(List.of("install", "-DskipTests"), Map.of()); assertThat(result.getProcess().waitFor()).isEqualTo(0); + final Path localRunner = getTargetDir().toPath().resolve("acme-quarkus-runner.jar"); + assertThat(localRunner).exists(); + + // make sure the runner jar was attached, there was a bug of the runner jar not being attached when the + // finalName option in the Quarkus plugin didn't match the finalName ocnfigured in the POM's build section + final Path installedRunner = running.getLocalRepositoryDirectory().toPath().resolve("org").resolve("acme") + .resolve("acme") + .resolve("1.0-SNAPSHOT").resolve("acme-1.0-SNAPSHOT-runner.jar"); + assertThat(installedRunner).exists(); + verifyUberJar(); } diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/deployment/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/deployment/pom.xml new file mode 100644 index 0000000000000..9a2863adf1ba0 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/deployment/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.example + extension-test-with-no-main + 1.0.0-SNAPSHOT + + runtime-deployment + Extension test with no main - Deployment + + + io.quarkus + quarkus-arc-deployment + + + org.example + runtime + \${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + \${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/pom.xml new file mode 100644 index 0000000000000..8e400633eec0b --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + org.example + extension-test-with-no-main + 1.0.0-SNAPSHOT + + extension-test-with-no-main-integration-tests + Extension test with no main - Integration Tests + + true + + + + io.quarkus + quarkus-resteasy + + + org.example + runtime + \${project.version} + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + \${project.build.directory}/\${project.build.finalName}-runner + org.jboss.logmanager.LogManager + \${maven.home} + + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + \${native.surefire.skip} + + + + + + false + native + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/src/test/java/org/example/ExampleTest.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/src/test/java/org/example/ExampleTest.java new file mode 100644 index 0000000000000..89b9cc85fd5a2 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/integration-tests/src/test/java/org/example/ExampleTest.java @@ -0,0 +1,12 @@ +package org.example; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class ExampleTest { + + @Test + public void test() {} + +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/pom.xml new file mode 100644 index 0000000000000..e92c9ddb51621 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + org.example + extension-test-with-no-main + 1.0.0-SNAPSHOT + pom + Extension test with no main + + deployment + runtime + integration-tests + + + 3.8.1 + ${surefire-plugin.version} + 11 + UTF-8 + UTF-8 + @project.version@ + 3.0.0-M7 + + + + + io.quarkus + quarkus-bom + \${quarkus.version} + pom + import + + + + + + + + io.quarkus + quarkus-maven-plugin + \${quarkus.version} + + + maven-surefire-plugin + \${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + \${maven.home} + \${settings.localRepository} + + + + + maven-failsafe-plugin + \${failsafe-plugin.version} + + + org.jboss.logmanager.LogManager + \${maven.home} + \${settings.localRepository} + + + + + maven-compiler-plugin + \${compiler-plugin.version} + + + -parameters + + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/runtime/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/runtime/pom.xml new file mode 100644 index 0000000000000..352593d4e387a --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-test-with-no-main/runtime/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.example + extension-test-with-no-main + 1.0.0-SNAPSHOT + + runtime + Extension test with no main - Runtime + + + io.quarkus + quarkus-arc + + + + + + io.quarkus + quarkus-extension-maven-plugin + \${quarkus.version} + + + compile + + extension-descriptor + + + \${project.groupId}:\${project.artifactId}-deployment:\${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + \${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/uberjar-maven-plugin-config/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/uberjar-maven-plugin-config/pom.xml index 3885f5e319342..4bb9c2d219eea 100644 --- a/integration-tests/maven/src/test/resources-filtered/projects/uberjar-maven-plugin-config/pom.xml +++ b/integration-tests/maven/src/test/resources-filtered/projects/uberjar-maven-plugin-config/pom.xml @@ -61,6 +61,9 @@ + + acme-quarkus + diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java index 1186847417960..44b184c5430e0 100644 --- a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java +++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java @@ -52,6 +52,10 @@ public String resolve(RoutingContext context) { return "tenant-autorefresh"; } + if (path.contains("tenant-refresh")) { + return "tenant-refresh"; + } + if (path.contains("tenant-https")) { if (context.getCookie("q_session_tenant-https_test") != null) { context.put("reauthenticated", "true"); diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantRefresh.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantRefresh.java new file mode 100644 index 0000000000000..aae7df4458b02 --- /dev/null +++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantRefresh.java @@ -0,0 +1,20 @@ +package io.quarkus.it.keycloak; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import io.quarkus.security.Authenticated; +import io.vertx.ext.web.RoutingContext; + +@Path("/tenant-refresh") +public class TenantRefresh { + @Inject + RoutingContext context; + + @Authenticated + @GET + public String getTenantRefresh() { + return "Tenant Refresh, refreshed: " + (context.get("refresh_token_grant_response") != null); + } +} diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties index 69b81d6eccce7..5b00192e9c8b6 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -74,10 +74,16 @@ quarkus.oidc.tenant-logout.client-id=quarkus-app quarkus.oidc.tenant-logout.credentials.secret=secret quarkus.oidc.tenant-logout.application-type=web-app quarkus.oidc.tenant-logout.authentication.cookie-path=/tenant-logout -quarkus.oidc.tenant-logout.authentication.session-age-extension=2M quarkus.oidc.tenant-logout.logout.path=/tenant-logout/logout quarkus.oidc.tenant-logout.logout.post-logout-path=/tenant-logout/post-logout -quarkus.oidc.tenant-logout.token.refresh-expired=true + +quarkus.oidc.tenant-refresh.auth-server-url=${keycloak.url}/realms/logout-realm +quarkus.oidc.tenant-refresh.client-id=quarkus-app +quarkus.oidc.tenant-refresh.credentials.secret=secret +quarkus.oidc.tenant-refresh.application-type=web-app +quarkus.oidc.tenant-refresh.authentication.cookie-path=/tenant-refresh +quarkus.oidc.tenant-refresh.authentication.session-age-extension=2M +quarkus.oidc.tenant-refresh.token.refresh-expired=true quarkus.oidc.tenant-autorefresh.auth-server-url=${keycloak.url}/realms/logout-realm quarkus.oidc.tenant-autorefresh.client-id=quarkus-app diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index a574802dbb855..ede67aad0d2bb 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -429,40 +429,41 @@ public void testRPInitiatedLogout() throws IOException { @Test public void testTokenRefresh() throws IOException { try (final WebClient webClient = createWebClient()) { - HtmlPage page = webClient.getPage("http://localhost:8081/tenant-logout"); + HtmlPage page = webClient.getPage("http://localhost:8081/tenant-refresh"); assertEquals("Sign in to logout-realm", page.getTitleText()); HtmlForm loginForm = page.getForms().get(0); loginForm.getInputByName("username").setValueAttribute("alice"); loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertEquals("Tenant Logout, refreshed: false", page.asText()); + assertEquals("Tenant Refresh, refreshed: false", page.asText()); - Cookie sessionCookie = getSessionCookie(webClient, "tenant-logout"); + Cookie sessionCookie = getSessionCookie(webClient, "tenant-refresh"); assertNotNull(sessionCookie); String idToken = getIdToken(sessionCookie); //wait now so that we reach the ID token timeout await().atMost(10, TimeUnit.SECONDS) - .pollInterval(Duration.ofSeconds(1)) + .pollInterval(Duration.ofSeconds(2)) .until(new Callable() { @Override public Boolean call() throws Exception { webClient.getOptions().setRedirectEnabled(false); WebResponse webResponse = webClient - .loadWebResponse(new WebRequest(URI.create("http://localhost:8081/tenant-logout").toURL())); + .loadWebResponse( + new WebRequest(URI.create("http://localhost:8081/tenant-refresh").toURL())); assertEquals(200, webResponse.getStatusCode()); // Should not redirect to OP but silently refresh token - Cookie newSessionCookie = getSessionCookie(webClient, "tenant-logout"); + Cookie newSessionCookie = getSessionCookie(webClient, "tenant-refresh"); assertNotNull(newSessionCookie); - return webResponse.getContentAsString().equals("Tenant Logout, refreshed: true") + return webResponse.getContentAsString().equals("Tenant Refresh, refreshed: true") && !idToken.equals(getIdToken(newSessionCookie)); } }); // local session refreshed and still valid - page = webClient.getPage("http://localhost:8081/tenant-logout"); - assertEquals("Tenant Logout, refreshed: false", page.asText()); - assertNotNull(getSessionCookie(webClient, "tenant-logout")); + page = webClient.getPage("http://localhost:8081/tenant-refresh"); + assertEquals("Tenant Refresh, refreshed: false", page.asText()); + assertNotNull(getSessionCookie(webClient, "tenant-refresh")); //wait now so that we reach the refresh timeout await().atMost(20, TimeUnit.SECONDS) @@ -472,12 +473,13 @@ public Boolean call() throws Exception { public Boolean call() throws Exception { webClient.getOptions().setRedirectEnabled(false); WebResponse webResponse = webClient - .loadWebResponse(new WebRequest(URI.create("http://localhost:8081/tenant-logout").toURL())); + .loadWebResponse( + new WebRequest(URI.create("http://localhost:8081/tenant-refresh").toURL())); // Should redirect to login page given that session is now expired at the OP int statusCode = webResponse.getStatusCode(); if (statusCode == 302) { - assertNull(getSessionCookie(webClient, "tenant-logout")); + assertNull(getSessionCookie(webClient, "tenant-refresh")); return true; } @@ -489,7 +491,7 @@ public Boolean call() throws Exception { webClient.getOptions().setRedirectEnabled(true); webClient.getCookieManager().clearCookies(); - page = webClient.getPage("http://localhost:8081/tenant-logout"); + page = webClient.getPage("http://localhost:8081/tenant-refresh"); assertNull(getSessionCookie(webClient, "tenant-logout")); assertEquals("Sign in to logout-realm", page.getTitleText()); webClient.getCookieManager().clearCookies(); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index a408c41796ba6..99dbb209ec2b2 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -288,6 +288,7 @@ reactive-messaging-rabbitmq reactive-messaging-rabbitmq-dyn reactive-messaging-hibernate-reactive + reactive-messaging-hibernate-orm rest-client resteasy-reactive-kotlin rest-client-reactive diff --git a/integration-tests/reactive-messaging-hibernate-orm/pom.xml b/integration-tests/reactive-messaging-hibernate-orm/pom.xml new file mode 100644 index 0000000000000..aa7cacfac2ba7 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/pom.xml @@ -0,0 +1,271 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-reactive-messaging-hibernate-orm + Quarkus - Integration Tests - Reactive Messaging - Hibernate ORM + The Reactive Messaging with Kafka and Hibernate integration tests module + + + true + + + + + io.quarkus + quarkus-integration-test-class-transformer + + + io.quarkus + quarkus-integration-test-shared-library + + + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.vertx + vertx-core + + + + + io.quarkus + quarkus-smallrye-health + + + + + io.quarkus + quarkus-kafka-client + + + io.quarkus + quarkus-smallrye-reactive-messaging-kafka + + + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-jdbc-postgresql + + + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-kafka-companion + test + + + io.rest-assured + rest-assured + test + + + jakarta.xml.bind + jakarta.xml.bind-api + + + + + + + io.quarkus + quarkus-integration-test-class-transformer-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-kafka-client-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-smallrye-health-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-smallrye-reactive-messaging-kafka-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-panache-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-postgresql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.smallrye.reactive + smallrye-reactive-messaging-api + + + org.awaitility + awaitility + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + generate-code + generate-code-tests + + + + + + + maven-failsafe-plugin + + true + + + + + maven-surefire-plugin + + true + + + + + + + + test-kafka + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + + diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/Fruit.java b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/Fruit.java new file mode 100644 index 0000000000000..da5f7a87c4829 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/Fruit.java @@ -0,0 +1,19 @@ +package io.quarkus.it.kafka; + +import javax.persistence.Entity; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Fruit extends PanacheEntity { + + public String name; + + public Fruit(String name) { + this.name = name; + } + + public Fruit() { + // Jackson will use this constructor + } +} diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/FruitProducer.java b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/FruitProducer.java new file mode 100644 index 0000000000000..9921a1e2fd469 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/FruitProducer.java @@ -0,0 +1,27 @@ +package io.quarkus.it.kafka; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.reactive.messaging.Channel; + +import io.smallrye.common.vertx.VertxContext; +import io.smallrye.mutiny.Uni; +import io.smallrye.reactive.messaging.MutinyEmitter; + +@Path("/kafka") +public class FruitProducer { + + @Channel("fruits-out") + MutinyEmitter emitter; + + @POST + @Path("/fruits") + @Consumes(MediaType.APPLICATION_JSON) + public Uni post(Fruit fruit) { + assert VertxContext.isOnDuplicatedContext(); + return emitter.send(fruit); + } +} diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java new file mode 100644 index 0000000000000..35f7b90ed512d --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java @@ -0,0 +1,23 @@ +package io.quarkus.it.kafka; + +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/kafka") +public class KafkaEndpoint { + @Inject + KafkaReceivers receivers; + + @GET + @Path("/fruits") + @Produces(MediaType.APPLICATION_JSON) + public List getFruits() { + return receivers.getFruits(); + } + +} diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java new file mode 100644 index 0000000000000..ad1ade1bff1d5 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java @@ -0,0 +1,32 @@ +package io.quarkus.it.kafka; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; +import javax.transaction.Transactional; + +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; + +import io.smallrye.reactive.messaging.MutinyEmitter; + +@ApplicationScoped +public class KafkaReceivers { + + @Channel("fruits-persisted") + MutinyEmitter emitter; + + @Incoming("fruits-in") + @Transactional + public CompletionStage persist(Message fruit) { + fruit.getPayload().persist(); + return emitter.sendMessage(fruit).subscribeAsCompletionStage(); + } + + public List getFruits() { + return Fruit.listAll(); + } + +} diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/main/resources/application.properties b/integration-tests/reactive-messaging-hibernate-orm/src/main/resources/application.properties new file mode 100644 index 0000000000000..165aa8b197ca4 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/main/resources/application.properties @@ -0,0 +1,13 @@ +quarkus.log.category.kafka.level=WARN +quarkus.log.category.\"org.apache.kafka\".level=WARN +quarkus.log.category.\"org.apache.zookeeper\".level=WARN + +# enable health check +quarkus.kafka.health.enabled=true + +kafka.auto.offset.reset=earliest + +quarkus.hibernate-orm.database.generation=drop-and-create + +mp.messaging.outgoing.fruits-out.topic=fruits +mp.messaging.incoming.fruits-in.topic=fruits diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorIT.java b/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorIT.java new file mode 100644 index 0000000000000..21c6143667af1 --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.kafka; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class KafkaConnectorIT extends KafkaConnectorTest { + +} diff --git a/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorTest.java b/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorTest.java new file mode 100644 index 0000000000000..5db5944fc335e --- /dev/null +++ b/integration-tests/reactive-messaging-hibernate-orm/src/test/java/io/quarkus/it/kafka/KafkaConnectorTest.java @@ -0,0 +1,60 @@ +package io.quarkus.it.kafka; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; + +import java.util.List; + +import javax.ws.rs.core.Response; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.kafka.InjectKafkaCompanion; +import io.quarkus.test.kafka.KafkaCompanionResource; +import io.restassured.common.mapper.TypeRef; +import io.restassured.http.ContentType; +import io.smallrye.reactive.messaging.kafka.companion.KafkaCompanion; + +@QuarkusTest +@QuarkusTestResource(KafkaCompanionResource.class) +public class KafkaConnectorTest { + + protected static final TypeRef> TYPE_REF = new TypeRef>() { + }; + + @InjectKafkaCompanion + KafkaCompanion companion; + + @Test + public void testFruits() { + given().body(new Fruit("apple")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + given().body(new Fruit("banana")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + given().body(new Fruit("peach")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + given().body(new Fruit("orange")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + given().body(new Fruit("cherry")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + given().body(new Fruit("pear")).contentType(ContentType.JSON).when().post("/kafka/fruits").then() + .assertThat().statusCode(is(Response.Status.NO_CONTENT.getStatusCode())); + + await().untilAsserted(() -> Assertions.assertEquals(6, get("/kafka/fruits").as(TYPE_REF).size())); + + for (ConsumerRecord record : companion + .consumeWithDeserializers(new StringDeserializer(), new StringDeserializer()) + .fromTopics("fruits-persisted", 6) + .awaitCompletion()) { + System.out.println(record); + } + } + +} diff --git a/integration-tests/reactive-messaging-hibernate-reactive/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java b/integration-tests/reactive-messaging-hibernate-reactive/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java index 2d5a5cd56f546..a52491f4ca07a 100644 --- a/integration-tests/reactive-messaging-hibernate-reactive/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java +++ b/integration-tests/reactive-messaging-hibernate-reactive/src/main/java/io/quarkus/it/kafka/KafkaReceivers.java @@ -4,6 +4,7 @@ import java.util.Objects; import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.control.ActivateRequestContext; import javax.inject.Inject; import org.eclipse.microprofile.reactive.messaging.Incoming; @@ -11,6 +12,7 @@ import org.eclipse.microprofile.reactive.messaging.Outgoing; import org.hibernate.reactive.mutiny.Mutiny; +import io.quarkus.hibernate.reactive.panache.Panache; import io.smallrye.common.vertx.ContextLocals; import io.smallrye.common.vertx.VertxContext; import io.smallrye.mutiny.Uni; @@ -38,10 +40,15 @@ public Uni> persistFruit(Message fruit) { @Blocking @Incoming("fruits-persisted") + @ActivateRequestContext public Uni consumeFruit(Message fruit) { assert VertxContext.isOnDuplicatedContext(); - assert Objects.equals(ContextLocals.get("fruit-id").get(), fruit.getPayload().id); - return Uni.createFrom().completionStage(fruit.ack()); + Fruit payload = fruit.getPayload(); + assert Objects.equals(ContextLocals.get("fruit-id").get(), payload.id); + return Panache.withTransaction(() -> { + payload.name = "fruit-" + payload.name; + return payload.persist().chain(() -> Uni.createFrom().completionStage(fruit.ack())); + }); } public Uni> getFruits() { diff --git a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java index 42c1949b690c2..11da8f2bbc9ea 100644 --- a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java +++ b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java @@ -120,6 +120,15 @@ static List testDataListArguments() { return Arrays.asList(Arguments.of(new TestData(), "foo"), Arguments.of(new TestData(), "foo")); } + @ParameterizedTest + @MethodSource("testDataUnmodifiableListArguments") + public void methodUnmodifiableListArguments(Object list) { + } + + static Stream testDataUnmodifiableListArguments() { + return Stream.of(Arguments.of(Collections.unmodifiableList(List.of(new TestObject3())))); + } + @ParameterizedTest @MethodSource("testStreamOfMapEntryArguments") public void methodList(Map.Entry ignore) { @@ -165,4 +174,10 @@ Map getMap() { } } + + static class TestObject3 { + + static final String value = "one"; + + } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultJarLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultJarLauncher.java index 10958e167ad83..c2bc57724a5d2 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultJarLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultJarLauncher.java @@ -165,6 +165,6 @@ public void includeAsSysProps(Map systemProps) { @Override public void close() { - quarkusProcess.destroy(); + LauncherUtil.destroyProcess(quarkusProcess); } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java index e61359e020238..2680369cdbe12 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java @@ -34,6 +34,7 @@ public class DefaultNativeImageLauncher implements NativeImageLauncher { private String testProfile; private List argLine; private String nativeImagePath; + private String configuredOutputDirectory; private Class testClass; private Process quarkusProcess; @@ -48,6 +49,7 @@ public void init(NativeImageInitContext initContext) { this.waitTimeSeconds = initContext.waitTime().getSeconds(); this.testProfile = initContext.testProfile(); this.nativeImagePath = initContext.nativeImagePath(); + this.configuredOutputDirectory = initContext.getConfiguredOutputDirectory(); this.argLine = initContext.argLine(); this.testClass = initContext.testClass(); } @@ -173,7 +175,7 @@ private void waitForStartedSupplier(Supplier startedSupplier, Process q } } - private static String guessPath(Class testClass) { + private String guessPath(Class testClass) { //ok, lets make a guess //this is a horrible hack, but it is intended to make this work in IDE's @@ -203,41 +205,47 @@ private static String guessPath(Class testClass) { "Unable to automatically find native image, please set the native.image.path to the native executable you wish to test"); } - private static String guessPath(final URL url) { + private String guessPath(final URL url) { if (url == null) { return null; } + String file = null; if (url.getProtocol().equals("file") && url.getPath().endsWith("test-classes/")) { //we have the maven test classes dir File testClasses = new File(url.getPath()); - for (File file : testClasses.getParentFile().listFiles()) { - if (isNativeExecutable(file)) { - logGuessedPath(file.getAbsolutePath()); - return file.getAbsolutePath(); - } - } + file = guessPathFromDir(testClasses.getParentFile()); } else if (url.getProtocol().equals("file") && url.getPath().endsWith("test/")) { //we have the gradle test classes dir, build/classes/java/test File testClasses = new File(url.getPath()); - for (File file : testClasses.getParentFile().getParentFile().getParentFile().listFiles()) { - if (isNativeExecutable(file)) { - logGuessedPath(file.getAbsolutePath()); - return file.getAbsolutePath(); - } - } + file = guessPathFromDir(testClasses.getParentFile().getParentFile().getParentFile()); } else if (url.getProtocol().equals("file") && url.getPath().contains("/target/surefire/")) { //this will make mvn failsafe:integration-test work String path = url.getPath(); int index = path.lastIndexOf("/target/"); File targetDir = new File(path.substring(0, index) + "/target/"); - for (File file : targetDir.listFiles()) { - if (isNativeExecutable(file)) { - logGuessedPath(file.getAbsolutePath()); - return file.getAbsolutePath(); - } - } + file = guessPathFromDir(targetDir); } + return file; + } + + private String guessPathFromDir(File dir) { + if (dir == null) { + return null; + } + if (configuredOutputDirectory != null) { + dir = dir.toPath().resolve(configuredOutputDirectory).toFile(); + } + File[] files = dir.listFiles(); + if (files == null) { + return null; + } + for (File file : files) { + if (isNativeExecutable(file)) { + logGuessedPath(file.getAbsolutePath()); + return file.getAbsolutePath(); + } + } return null; } @@ -267,6 +275,6 @@ public void includeAsSysProps(Map systemProps) { @Override public void close() { - quarkusProcess.destroy(); + LauncherUtil.destroyProcess(quarkusProcess); } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java index d07cf27b0016a..76c2701fadded 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java @@ -110,7 +110,7 @@ private static void ensureProcessIsAlive(Process quarkusProcess) { * Try to destroy the process normally a few times * and resort to forceful destruction if necessary */ - private static void destroyProcess(Process quarkusProcess) { + static void destroyProcess(Process quarkusProcess) { quarkusProcess.destroy(); int i = 0; while (i++ < 10) { diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java index 5121ceebd9545..3ade1d9704460 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java @@ -6,6 +6,8 @@ interface NativeImageInitContext extends InitContext { String nativeImagePath(); + String getConfiguredOutputDirectory(); + Class testClass(); } } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index 9021f60b113b7..629dd7c3df92f 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -12,6 +12,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.enterprise.inject.Alternative; @@ -59,20 +60,21 @@ protected PrepareResult createAugmentor(ExtensionContext context, Class addToBuilderIfConditionMet = path -> { + if (path != null && Files.exists(path) && !rootBuilder.contains(path)) { + rootBuilder.add(path); + } + }; + if (!appClassLocation.equals(testClassLocation)) { - rootBuilder.add(testClassLocation); + addToBuilderIfConditionMet.accept(testClassLocation); // if test classes is a dir, we should also check whether test resources dir exists as a separate dir (gradle) // TODO: this whole app/test path resolution logic is pretty dumb, it needs be re-worked using proper workspace discovery final Path testResourcesLocation = PathTestHelper.getResourcesForClassesDirOrNull(testClassLocation, "test"); - if (testResourcesLocation != null) { - rootBuilder.add(testResourcesLocation); - } + addToBuilderIfConditionMet.accept(testResourcesLocation); } originalCl = Thread.currentThread().getContextClassLoader(); - Map sysPropRestore = new HashMap<>(); - sysPropRestore.put(ProfileManager.QUARKUS_TEST_PROFILE_PROP, - System.getProperty(ProfileManager.QUARKUS_TEST_PROFILE_PROP)); // clear the test.url system property as the value leaks into the run when using different profiles System.clearProperty("test.url"); @@ -99,17 +101,15 @@ protected PrepareResult createAugmentor(ExtensionContext context, Class testClass; + private final String configuredOutputDirectory; public DefaultNativeImageInitContext(int httpPort, int httpsPort, Duration waitTime, String testProfile, List argLine, ArtifactLauncher.InitContext.DevServicesLaunchResult devServicesLaunchResult, - String nativeImagePath, Class testClass) { + String nativeImagePath, String configuredOutputDirectory, Class testClass) { super(httpPort, httpsPort, waitTime, testProfile, argLine, devServicesLaunchResult); this.nativeImagePath = nativeImagePath; + this.configuredOutputDirectory = configuredOutputDirectory; this.testClass = testClass; } @@ -71,6 +74,11 @@ public String nativeImagePath() { return nativeImagePath; } + @Override + public String getConfiguredOutputDirectory() { + return configuredOutputDirectory; + } + @Override public Class testClass() { return testClass;