diff --git a/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java index fc4e1e776034c..6c456cd795baf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java @@ -42,7 +42,7 @@ public class BootstrapConfig { boolean disableJarCache; /** - * A temporary option introduced to avoid a logging warning when {@code }-Dquarkus.bootstrap.incubating-model-resolver} + * A temporary option introduced to avoid a logging warning when {@code -Dquarkus.bootstrap.incubating-model-resolver} * is added to the build command line. * This option enables an incubating implementation of the Quarkus Application Model resolver. * This option will be removed as soon as the incubating implementation becomes the default one. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java index 36dae8b38f8f3..b0d2945ea5f43 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java @@ -227,7 +227,8 @@ public static void dumpCurrentConfigValues(ApplicationModel appModel, String lau if (previouslyRecordedProperties.isEmpty()) { try { readConfig(appModel, mode, buildSystemProps, deploymentClassLoader, configReader -> { - var config = configReader.initConfiguration(mode, buildSystemProps, appModel.getPlatformProperties()); + var config = configReader.initConfiguration(mode, buildSystemProps, new Properties(), + appModel.getPlatformProperties()); final Map allProps = new HashMap<>(); for (String name : config.getPropertyNames()) { allProps.put(name, ConfigTrackingValueTransformer.asString(config.getConfigValue(name))); @@ -287,7 +288,8 @@ public static void dumpCurrentConfigValues(ApplicationModel appModel, String lau public static Config getConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps, QuarkusClassLoader deploymentClassLoader) throws CodeGenException { return readConfig(appModel, launchMode, buildSystemProps, deploymentClassLoader, - configReader -> configReader.initConfiguration(launchMode, buildSystemProps, appModel.getPlatformProperties())); + configReader -> configReader.initConfiguration(launchMode, buildSystemProps, new Properties(), + appModel.getPlatformProperties())); } public static T readConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps, diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 6549aed18394b..6d7bc5bd526c9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -132,12 +132,14 @@ private static boolean isRecorder(AnnotatedElement element) { * @throws IOException if the class loader could not load a resource * @throws ClassNotFoundException if a build step class is not found */ - public static Consumer loadStepsFrom(ClassLoader classLoader, Properties buildSystemProps, + public static Consumer loadStepsFrom(ClassLoader classLoader, + Properties buildSystemProps, Properties runtimeProperties, ApplicationModel appModel, LaunchMode launchMode, DevModeType devModeType) throws IOException, ClassNotFoundException { final BuildTimeConfigurationReader reader = new BuildTimeConfigurationReader(classLoader); - final SmallRyeConfig src = reader.initConfiguration(launchMode, buildSystemProps, appModel.getPlatformProperties()); + final SmallRyeConfig src = reader.initConfiguration(launchMode, buildSystemProps, runtimeProperties, + appModel.getPlatformProperties()); // install globally QuarkusConfigFactory.setConfig(src); final BuildTimeConfigurationReader.ReadResult readResult = reader.readConfiguration(src); 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 ef13a5eeb0ea8..b423d83378756 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -55,6 +55,7 @@ public class QuarkusAugmentor { private final Collection excludedFromIndexing; private final LiveReloadBuildItem liveReloadBuildItem; private final Properties buildSystemProperties; + private final Properties runtimeProperties; private final Path targetDir; private final ApplicationModel effectiveModel; private final Supplier depInfoProvider; @@ -75,6 +76,7 @@ public class QuarkusAugmentor { this.excludedFromIndexing = builder.excludedFromIndexing; this.liveReloadBuildItem = builder.liveReloadState; this.buildSystemProperties = builder.buildSystemProperties; + this.runtimeProperties = builder.runtimeProperties; this.targetDir = builder.targetDir; this.effectiveModel = builder.effectiveModel; this.baseName = builder.baseName; @@ -102,13 +104,9 @@ public BuildResult run() throws Exception { final BuildChainBuilder chainBuilder = BuildChain.builder(); chainBuilder.setClassLoader(deploymentClassLoader); - //provideCapabilities(chainBuilder); - - //TODO: we load everything from the deployment class loader - //this allows the deployment config (application.properties) to be loaded, but in theory could result - //in additional stuff from the deployment leaking in, this is unlikely but has a bit of a smell. ExtensionLoader.loadStepsFrom(deploymentClassLoader, buildSystemProperties == null ? new Properties() : buildSystemProperties, + runtimeProperties == null ? new Properties() : runtimeProperties, effectiveModel, launchMode, devModeType) .accept(chainBuilder); @@ -210,6 +208,7 @@ public static final class Builder { LaunchMode launchMode = LaunchMode.NORMAL; LiveReloadBuildItem liveReloadState = new LiveReloadBuildItem(); Properties buildSystemProperties; + Properties runtimeProperties; ApplicationModel effectiveModel; String baseName = QUARKUS_APPLICATION; @@ -322,6 +321,15 @@ public Builder setBuildSystemProperties(final Properties buildSystemProperties) return this; } + public Properties getRuntimeProperties() { + return runtimeProperties; + } + + public Builder setRuntimeProperties(final Properties runtimeProperties) { + this.runtimeProperties = runtimeProperties; + return this; + } + public Builder setRebuild(boolean rebuild) { this.rebuild = rebuild; return this; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveFieldBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveFieldBuildItem.java index 28ae2f07a6b94..ecfdfd7e491b3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveFieldBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveFieldBuildItem.java @@ -13,9 +13,7 @@ public final class ReflectiveFieldBuildItem extends MultiBuildItem { final String reason; public ReflectiveFieldBuildItem(String reason, FieldInfo field) { - this.reason = reason; - this.name = field.name(); - this.declaringClass = field.declaringClass().name().toString(); + this(reason, field.declaringClass().name().toString(), field.name()); } public ReflectiveFieldBuildItem(FieldInfo field) { @@ -27,9 +25,13 @@ public ReflectiveFieldBuildItem(Field field) { } public ReflectiveFieldBuildItem(String reason, Field field) { + this(reason, field.getDeclaringClass().getName(), field.getName()); + } + + public ReflectiveFieldBuildItem(String reason, String declaringClass, String fieldName) { this.reason = reason; - this.name = field.getName(); - this.declaringClass = field.getDeclaringClass().getName(); + this.name = fieldName; + this.declaringClass = declaringClass; } public String getDeclaringClass() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java index e42d94cbb07b6..e493211f6336e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -380,7 +380,7 @@ public List getBuildTimeVisibleMappings() { * @param platformProperties Quarkus platform properties to add as a configuration source * @return configuration instance */ - public SmallRyeConfig initConfiguration(LaunchMode launchMode, Properties buildSystemProps, + public SmallRyeConfig initConfiguration(LaunchMode launchMode, Properties buildSystemProps, Properties runtimeProperties, Map platformProperties) { // now prepare & load the build configuration SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(false, launchMode); @@ -388,17 +388,17 @@ public SmallRyeConfig initConfiguration(LaunchMode launchMode, Properties buildS builder.forClassLoader(classLoader); } - DefaultValuesConfigurationSource ds1 = new DefaultValuesConfigurationSource(getBuildTimePatternMap()); - DefaultValuesConfigurationSource ds2 = new DefaultValuesConfigurationSource(getBuildTimeRunTimePatternMap()); - PropertiesConfigSource pcs = new PropertiesConfigSource(buildSystemProps, "Build system"); - if (platformProperties.isEmpty()) { - builder.withSources(ds1, ds2, pcs); - } else { + builder + .withSources(new DefaultValuesConfigurationSource(getBuildTimePatternMap())) + .withSources(new DefaultValuesConfigurationSource(getBuildTimeRunTimePatternMap())) + .withSources(new PropertiesConfigSource(buildSystemProps, "Build system")) + .withSources(new PropertiesConfigSource(runtimeProperties, "Runtime Properties")); + + if (!platformProperties.isEmpty()) { // Our default value configuration source is using an ordinal of Integer.MIN_VALUE // (see io.quarkus.deployment.configuration.DefaultValuesConfigurationSource) - DefaultValuesConfigSource platformConfigSource = new DefaultValuesConfigSource(platformProperties, - "Quarkus platform", Integer.MIN_VALUE + 1000); - builder.withSources(ds1, ds2, platformConfigSource, pcs); + builder.withSources( + new DefaultValuesConfigSource(platformProperties, "Quarkus platform", Integer.MIN_VALUE + 1000)); } for (ConfigClassWithPrefix mapping : getBuildTimeVisibleMappings()) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/filesystem/QuarkusFileManager.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/filesystem/QuarkusFileManager.java index 4ac5314804078..71bd65e54830d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/filesystem/QuarkusFileManager.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/filesystem/QuarkusFileManager.java @@ -22,6 +22,8 @@ protected QuarkusFileManager(StandardJavaFileManager fileManager, Context contex this.fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(context.getGeneratedSourcesDirectory())); } if (context.getAnnotationProcessorPaths() != null) { + // Paths might be missing! (see: https://github.com/quarkusio/quarkus/issues/42908) + ensureDirectories(context.getAnnotationProcessorPaths()); this.fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, context.getAnnotationProcessorPaths()); } } catch (IOException e) { @@ -39,6 +41,8 @@ public void reset(Context context) { this.fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, List.of(context.getGeneratedSourcesDirectory())); } if (context.getAnnotationProcessorPaths() != null) { + // Paths might be missing! (see: https://github.com/quarkusio/quarkus/issues/42908) + ensureDirectories(context.getAnnotationProcessorPaths()); this.fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, context.getAnnotationProcessorPaths()); } } catch (IOException e) { @@ -46,6 +50,17 @@ public void reset(Context context) { } } + private void ensureDirectories(Iterable directories) { + for (File directory : directories) { + if (!directory.exists()) { + final boolean success = directory.mkdirs(); + if (!success) { + throw new RuntimeException("Cannot create directory " + directory); + } + } + } + } + @Override public void close() throws IOException { super.close(); 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 4fe1d0469bd19..9d94bcac05481 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 @@ -51,6 +51,7 @@ public void accept(CuratedApplication curatedApplication, Map re .setTargetDir(quarkusBootstrap.getTargetDirectory()) .setDeploymentClassLoader(curatedApplication.createDeploymentClassLoader()) .setBuildSystemProperties(quarkusBootstrap.getBuildSystemProperties()) + .setRuntimeProperties(quarkusBootstrap.getRuntimeProperties()) .setEffectiveModel(curatedApplication.getApplicationModel()); if (quarkusBootstrap.getBaseName() != null) { builder.setBaseName(quarkusBootstrap.getBaseName()); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index ecdacc7f44616..9b5a17a380853 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -57,6 +57,7 @@ import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem; import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.deployment.naming.NamingConfig; import io.quarkus.deployment.pkg.PackageConfig; @@ -96,6 +97,9 @@ public class MainClassBuildStep { static final String STARTUP_CONTEXT = "STARTUP_CONTEXT"; static final String LOG = "LOG"; static final String JAVA_LIBRARY_PATH = "java.library.path"; + // This is declared as a constant so that it can be grepped for in the native-image binary using `strings`, e.g.: + // strings ./target/quarkus-runner | grep "__quarkus_analytics__quarkus.version=" + public static final String QUARKUS_ANALYTICS_QUARKUS_VERSION = "__QUARKUS_ANALYTICS_QUARKUS_VERSION"; public static final String GENERATE_APP_CDS_SYSTEM_PROPERTY = "quarkus.appcds.generate"; @@ -155,6 +159,9 @@ void build(List staticInitTasks, FieldCreator scField = file.getFieldCreator(STARTUP_CONTEXT_FIELD); scField.setModifiers(Modifier.PUBLIC | Modifier.STATIC); + FieldCreator quarkusVersionField = file.getFieldCreator(QUARKUS_ANALYTICS_QUARKUS_VERSION, String.class) + .setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL); + MethodCreator ctor = file.getMethodCreator("", void.class); ctor.invokeSpecialMethod(ofMethod(Application.class, "", void.class, boolean.class), ctor.getThis(), ctor.load(launchMode.isAuxiliaryApplication())); @@ -192,6 +199,10 @@ void build(List staticInitTasks, mv.writeStaticField(logField.getFieldDescriptor(), mv.invokeStaticMethod( ofMethod(Logger.class, "getLogger", Logger.class, String.class), mv.load("io.quarkus.application"))); + // Init the __QUARKUS_ANALYTICS_QUARKUS_VERSION field + mv.writeStaticField(quarkusVersionField.getFieldDescriptor(), + mv.load("__quarkus_analytics__quarkus.version=" + Version.getVersion())); + ResultHandle startupContext = mv.newInstance(ofConstructor(StartupContext.class)); mv.writeStaticField(scField.getFieldDescriptor(), startupContext); TryBlock tryBlock = mv.tryBlock(); @@ -703,4 +714,10 @@ private static Result invalid() { } } + @BuildStep + ReflectiveFieldBuildItem setupVersionField() { + return new ReflectiveFieldBuildItem( + "Ensure it's included in the executable to be able to grep the quarkus version", + Application.APP_CLASS_NAME, QUARKUS_ANALYTICS_QUARKUS_VERSION); + } } diff --git a/core/launcher/src/main/java/io/quarkus/launcher/JBangIntegration.java b/core/launcher/src/main/java/io/quarkus/launcher/JBangIntegration.java index 7e7cf1fb12cb2..e0adc8e68e543 100644 --- a/core/launcher/src/main/java/io/quarkus/launcher/JBangIntegration.java +++ b/core/launcher/src/main/java/io/quarkus/launcher/JBangIntegration.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import io.quarkus.bootstrap.BootstrapConstants; @@ -46,6 +47,7 @@ public static Map postBuild(Path appClasses, Path pomFile, List< return ret; } + Properties configurationProperties = new Properties(); for (String comment : comments) { //we allow config to be provided via //Q:CONFIG name=value if (comment.startsWith(CONFIG)) { @@ -54,7 +56,7 @@ public static Map postBuild(Path appClasses, Path pomFile, List< if (equals == -1) { throw new RuntimeException("invalid config " + comment); } - System.setProperty(conf.substring(0, equals), conf.substring(equals + 1)); + configurationProperties.setProperty(conf.substring(0, equals), conf.substring(equals + 1)); } } @@ -117,12 +119,15 @@ public Enumeration getResources(String name) throws IOException { Thread.currentThread().setContextClassLoader(loader); Class launcher = loader.loadClass("io.quarkus.bootstrap.jbang.JBangBuilderImpl"); return (Map) launcher - .getDeclaredMethod("postBuild", Path.class, Path.class, List.class, List.class, boolean.class).invoke( + .getDeclaredMethod("postBuild", Path.class, Path.class, List.class, List.class, Properties.class, + boolean.class) + .invoke( null, appClasses, pomFile, repositories, dependencies, + configurationProperties, nativeImage); } catch (Exception e) { throw new RuntimeException(e); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java index 01d845e500a40..c2a3b9bf223d0 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java @@ -70,7 +70,7 @@ public static ResolvedType makeMap(TypeMirror type, ResolvedType unwrappedResolv return new ResolvedType(type, unwrappedResolvedType.unwrappedType, unwrappedResolvedType.binaryName, unwrappedResolvedType.qualifiedName, unwrappedResolvedType.simplifiedName, unwrappedResolvedType.isPrimitive, - true, unwrappedResolvedType.isList, + true, false, unwrappedResolvedType.isOptional, unwrappedResolvedType.isDeclared, unwrappedResolvedType.isInterface, unwrappedResolvedType.isClass, unwrappedResolvedType.isEnum, unwrappedResolvedType.isDuration, unwrappedResolvedType.isConfigGroup); diff --git a/docs/src/main/asciidoc/ide-tooling.adoc b/docs/src/main/asciidoc/ide-tooling.adoc index d9531c39808de..f7b1f1dae9e29 100644 --- a/docs/src/main/asciidoc/ide-tooling.adoc +++ b/docs/src/main/asciidoc/ide-tooling.adoc @@ -22,10 +22,10 @@ In addition, IntelliJ IDEA has additional support for Quarkus in their Ultimate The table below gives an overview of the current IDEs with links and a high-level overview of their features. -:vscode-logo: https://simpleicons.org/icons/visualstudiocode.svg -:eclipse-logo: https://simpleicons.org/icons/eclipseide.svg -:intellij-logo: https://simpleicons.org/icons/intellijidea.svg -:che-logo: https://simpleicons.org/icons/eclipseche.svg +:vscode-logo: visualstudiocode.svg +:eclipse-logo: eclipseide.svg +:intellij-logo: intellijidea.svg +:che-logo: eclipseche.svg [cols="6*^", header] |=== | . diff --git a/docs/src/main/asciidoc/images/eclipseche.svg b/docs/src/main/asciidoc/images/eclipseche.svg new file mode 100644 index 0000000000000..904aff3755a4a --- /dev/null +++ b/docs/src/main/asciidoc/images/eclipseche.svg @@ -0,0 +1 @@ +Eclipse Che \ No newline at end of file diff --git a/docs/src/main/asciidoc/images/eclipseide.svg b/docs/src/main/asciidoc/images/eclipseide.svg new file mode 100644 index 0000000000000..80a9282e05c84 --- /dev/null +++ b/docs/src/main/asciidoc/images/eclipseide.svg @@ -0,0 +1 @@ +Eclipse IDE \ No newline at end of file diff --git a/docs/src/main/asciidoc/images/intellijidea.svg b/docs/src/main/asciidoc/images/intellijidea.svg new file mode 100644 index 0000000000000..6cc5f0f214ddb --- /dev/null +++ b/docs/src/main/asciidoc/images/intellijidea.svg @@ -0,0 +1 @@ +IntelliJ IDEA \ No newline at end of file diff --git a/docs/src/main/asciidoc/images/visualstudiocode.svg b/docs/src/main/asciidoc/images/visualstudiocode.svg new file mode 100644 index 0000000000000..cb4cb1501cc30 --- /dev/null +++ b/docs/src/main/asciidoc/images/visualstudiocode.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/src/main/asciidoc/rest-data-panache.adoc b/docs/src/main/asciidoc/rest-data-panache.adoc index ff002dbb2db72..ac5c22cc5becb 100644 --- a/docs/src/main/asciidoc/rest-data-panache.adoc +++ b/docs/src/main/asciidoc/rest-data-panache.adoc @@ -13,7 +13,6 @@ include::_attributes.adoc[] A lot of web applications are monotonous CRUD applications with REST APIs that are tedious to write. To streamline this task, REST Data with Panache extension can generate the basic CRUD endpoints for your entities and repositories. -While this extension is still experimental and provides a limited feature set, we hope to get an early feedback for it. Currently, this extension supports Hibernate ORM and MongoDB with Panache and can generate CRUD resources that work with `application/json` and `application/hal+json` content. == Setting up REST Data with Panache @@ -23,17 +22,20 @@ Please, check out the next compatibility table to use the right one according to .Compatibility Table |=== -|Extension |Hibernate | RESTEasy +|Extension |Status |Hibernate |RESTEasy |<> +|`Stable` |`ORM` |`Classic and Reactive` |<> +|`Experimental` |`Reactive` |`Reactive` |<> +|`Experimental` |`ORM` |`Classic and Reactive` |=== diff --git a/docs/src/main/asciidoc/rest.adoc b/docs/src/main/asciidoc/rest.adoc index e38650b354d85..81aca81ff5ad4 100644 --- a/docs/src/main/asciidoc/rest.adoc +++ b/docs/src/main/asciidoc/rest.adoc @@ -1391,9 +1391,28 @@ In both cases, importing those modules will allow HTTP message bodies to be read and serialised to JSON, for <>. -==== Advanced Jackson-specific features +==== Jackson-specific features -When using the `quarkus-rest-jackson` extension there are some advanced features that Quarkus REST supports. +===== Exception handling + +By default, Quarkus provides a built-in `ExceptionMapper` for `MismatchedInputException` which returns an HTTP 400 status code +along with a good error message in Dev and Test modes, about what went wrong during serialization of an entity. + +[NOTE] +==== +There are situations where various Jackson related exceptions need to handled in a uniform way.For example, the application may need to handle all `JsonMappingException` the same way. +This becomes a problem when taking JAX-RS / Jakarta REST rules into account, because the exception mapper `ExceptionMapper` for `MismatchedInputException` would be used instead of the user provide +`ExceptionMapper` for `JsonMappingException` (as `MismatchedInputException` is a subtype of `JsonMappingException`). + +One solution for this case is to configure the following: + +[source,properties] +---- +quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.class +---- + +which essentially makes Quarkus ignore the `ExceptionMapper` for `MismatchedInputException` completely. +==== [[secure-serialization]] ===== Secure serialization diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 386200105bd09..3d37398a9be5f 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -71,7 +71,6 @@ import io.quarkus.resteasy.reactive.jackson.EnableSecureSerialization; import io.quarkus.resteasy.reactive.jackson.SecureField; import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder; -import io.quarkus.resteasy.reactive.jackson.runtime.mappers.DefaultMismatchedInputException; import io.quarkus.resteasy.reactive.jackson.runtime.mappers.NativeInvalidDefinitionExceptionMapper; import io.quarkus.resteasy.reactive.jackson.runtime.security.RolesAllowedConfigExpStorage; import io.quarkus.resteasy.reactive.jackson.runtime.security.SecurityCustomSerialization; @@ -110,6 +109,7 @@ public class ResteasyReactiveJacksonProcessor { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON, APPLICATION_NDJSON, APPLICATION_STREAM_JSON); + public static final String DEFAULT_MISMATCHED_INPUT_EXCEPTION = "io.quarkus.resteasy.reactive.jackson.runtime.mappers.BuiltinMismatchedInputExceptionMapper"; @BuildStep void feature(BuildProducer feature) { @@ -132,9 +132,16 @@ ReinitializeVertxJsonBuildItem vertxJson() { } @BuildStep - ExceptionMapperBuildItem exceptionMappers() { - return new ExceptionMapperBuildItem(DefaultMismatchedInputException.class.getName(), - MismatchedInputException.class.getName(), Priorities.USER + 100, false); + void exceptionMappers(BuildProducer producer) { + try { + Thread.currentThread().getContextClassLoader().loadClass(DEFAULT_MISMATCHED_INPUT_EXCEPTION); + } catch (NoClassDefFoundError | ClassNotFoundException e) { + // the class is not available, likely due to quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class + return; + } + + producer.produce(new ExceptionMapperBuildItem(DEFAULT_MISMATCHED_INPUT_EXCEPTION, + MismatchedInputException.class.getName(), Priorities.USER + 100, false)); } @BuildStep diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java new file mode 100644 index 0000000000000..e334002585c07 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java @@ -0,0 +1,46 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import java.util.function.Supplier; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +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 com.fasterxml.jackson.databind.DatabindException; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(FroMage.class, FroMageEndpoint.class, DatabindExceptionMapper.class); + } + }).overrideConfigKey("quarkus.class-loading.removed-resources.\"io.quarkus\\:quarkus-rest-jackson\"", + "io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.class"); + + @Test + public void test() { + RestAssured.with().contentType("application/json").body("{\"name\": \"brie\"}").put("/fromage") + .then().statusCode(999); + } + + @Provider + public static class DatabindExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(DatabindException exception) { + return Response.status(999).build(); + } + } +} diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java similarity index 98% rename from extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java rename to extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java index 54783b513b961..e9fe48c834fca 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java @@ -10,7 +10,8 @@ import io.quarkus.runtime.LaunchMode; -public class DefaultMismatchedInputException +@SuppressWarnings("unused") +public class BuiltinMismatchedInputExceptionMapper implements ExceptionMapper { @Override 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 966629bcc767e..35f44526d7eab 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 List excludeFromClassPath; private final Properties buildSystemProperties; + private final Properties runtimeProperties; private final String baseName; private final String originalBaseName; private final Path targetDirectory; @@ -100,6 +101,7 @@ private QuarkusBootstrap(Builder builder) { this.excludeFromClassPath = new ArrayList<>(builder.excludeFromClassPath); this.projectRoot = builder.projectRoot != null ? builder.projectRoot.normalize() : null; this.buildSystemProperties = builder.buildSystemProperties != null ? builder.buildSystemProperties : new Properties(); + this.runtimeProperties = builder.runtimeProperties != null ? builder.runtimeProperties : new Properties(); this.mode = builder.mode; this.offline = builder.offline; this.test = builder.test; @@ -202,6 +204,10 @@ public Properties getBuildSystemProperties() { return buildSystemProperties; } + public Properties getRuntimeProperties() { + return runtimeProperties; + } + public Path getProjectRoot() { return projectRoot; } @@ -266,6 +272,7 @@ public Builder clonedBuilder() { .setProjectRoot(projectRoot) .setBaseClassLoader(baseClassLoader) .setBuildSystemProperties(buildSystemProperties) + .setRuntimeProperties(runtimeProperties) .setMode(mode) .setTest(test) .setLocalProjectDiscovery(localProjectDiscovery) @@ -316,6 +323,7 @@ public static class Builder { final List additionalDeploymentArchives = new ArrayList<>(); final List excludeFromClassPath = new ArrayList<>(); Properties buildSystemProperties; + Properties runtimeProperties; Mode mode = Mode.PROD; Boolean offline; boolean test; @@ -389,6 +397,11 @@ public Builder setBuildSystemProperties(Properties buildSystemProperties) { return this; } + public Builder setRuntimeProperties(Properties runtimeProperties) { + this.runtimeProperties = runtimeProperties; + return this; + } + public Builder setOffline(boolean offline) { this.offline = offline; return this; diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangBuilderImpl.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangBuilderImpl.java index 4b5c60c0bdd93..26fb7f706b243 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangBuilderImpl.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangBuilderImpl.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; import org.eclipse.aether.repository.RemoteRepository; @@ -26,8 +27,12 @@ import io.quarkus.maven.dependency.ResolvedArtifactDependency; public class JBangBuilderImpl { - public static Map postBuild(Path appClasses, Path pomFile, List> repositories, + public static Map postBuild( + Path appClasses, + Path pomFile, + List> repositories, List> dependencies, + Properties configurationProperties, boolean nativeImage) { final MavenArtifactResolver quarkusResolver; try { @@ -80,6 +85,8 @@ public static Map postBuild(Path appClasses, Path pomFile, List< }).collect(Collectors.toList())) .setAppArtifact(appArtifact) .setIsolateDeployment(true) + .setBuildSystemProperties(configurationProperties) + .setRuntimeProperties(configurationProperties) .setMode(QuarkusBootstrap.Mode.PROD); CuratedApplication app = builder diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml index 6f75152063220..03e98f152206a 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml @@ -25,7 +25,7 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest {group-id} diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml index fee0d49c79ac2..d9f77b21dfc98 100644 --- a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml @@ -17,7 +17,7 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest io.quarkiverse.my-quarkiverse-ext diff --git a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java index 757e4455e200e..6ec053fa440cc 100644 --- a/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java +++ b/integration-tests/opentelemetry-reactive/src/test/java/io/quarkus/it/opentelemetry/reactive/OpenTelemetryReactiveTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; @@ -41,8 +42,14 @@ public class OpenTelemetryReactiveTest { @BeforeEach @AfterEach void reset() { - given().get("/reset").then().statusCode(HTTP_OK); - await().atMost(5, SECONDS).until(() -> getSpans().size() == 0); + await().atMost(Duration.ofSeconds(30L)).until(() -> { + // make sure spans are cleared + List> spans = getSpans(); + if (!spans.isEmpty()) { + given().get("/reset").then().statusCode(HTTP_OK); + } + return spans.isEmpty(); + }); } @Test diff --git a/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java b/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java index 94e7cb3fd6428..8148595632a0f 100644 --- a/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java +++ b/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java @@ -4,7 +4,6 @@ import static io.restassured.RestAssured.get; import static io.restassured.RestAssured.given; import static java.net.HttpURLConnection.HTTP_OK; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.*; @@ -33,8 +32,14 @@ public class OTelSpiTest { @BeforeEach @AfterEach void reset() { - given().get("/reset").then().statusCode(HTTP_OK); - await().atMost(5, SECONDS).until(() -> getSpans().size() == 0); + await().atMost(Duration.ofSeconds(30L)).until(() -> { + // make sure spans are cleared + List> spans = getSpans(); + if (!spans.isEmpty()) { + given().get("/reset").then().statusCode(HTTP_OK); + } + return spans.isEmpty(); + }); } private List> getSpans() { diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java index 9430bd7583a3c..b945bbddf22ef 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryInjectionsTest.java @@ -6,6 +6,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; +import java.time.Duration; import java.util.List; import java.util.Map; @@ -22,8 +23,14 @@ public class OpenTelemetryInjectionsTest { @BeforeEach @AfterEach void reset() { - given().get("/reset").then().statusCode(HTTP_OK); - await().atMost(5, SECONDS).until(() -> getSpans().size() == 0); + await().atMost(Duration.ofSeconds(30L)).until(() -> { + // make sure spans are cleared + List> spans = getSpans(); + if (!spans.isEmpty()) { + given().get("/reset").then().statusCode(HTTP_OK); + } + return spans.isEmpty(); + }); } private List> getSpans() {