diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4a81a143a8e7c..71f13962f253d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -43,7 +43,7 @@ 1.2 1.0 1.6.0 - 2.4.3 + 2.4.4 3.1.1 3.0.1 2.1.10 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java index cdb39a59eb6e7..8367de53f3438 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java @@ -90,7 +90,10 @@ private void setupPausedConsole() { public void run() { if (lastResults == null) { testsStatusOutput.setMessage(BLUE + "Starting tests" + RESET); + } else { + testsStatusOutput.setMessage(null); } + setupTestsRunningConsole(); TestSupport.instance().get().start(); } })); @@ -103,8 +106,10 @@ private void setupFirstRunConsole() { } else { testsStatusOutput.setMessage(BLUE + "Running tests for the first time" + RESET); } - consoleContext.reset(); - addTestOutput(); + if (firstRun) { + consoleContext.reset(); + addTestOutput(); + } } void addTestOutput() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index dacefc389d66c..4709fdaac3ad3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -538,13 +538,53 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), currentThread); - ResultHandle objectClass = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load("java.lang.Object"), tc.load(false), tccl); + MethodDescriptor forNameMethodDescriptor = ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, + ClassLoader.class); + + MethodDescriptor lookupMethod = ofMethod("com.oracle.svm.util.ReflectionUtil", "lookupMethod", Method.class, + Class.class, String.class, + Class[].class); + MethodDescriptor invokeMethodDescriptor = ofMethod(Method.class, "invoke", Object.class, Object.class, + Object[].class); + + BranchResult graalVm21_3Test = tc.ifGreaterEqualZero( + tc.invokeVirtualMethod(VERSION_COMPARE_TO, tc.invokeStaticMethod(VERSION_CURRENT), + tc.marshalAsArray(int.class, tc.load(21), tc.load(3)))); + + BytecodeCreator greaterThan21_3 = graalVm21_3Test.trueBranch(); + ResultHandle runtimeSerializationClass = greaterThan21_3.invokeStaticMethod(forNameMethodDescriptor, + greaterThan21_3.load("org.graalvm.nativeimage.hosted.RuntimeSerialization"), + greaterThan21_3.load(false), tccl); + ResultHandle registerArgTypes = greaterThan21_3.newArray(Class.class, greaterThan21_3.load(1)); + greaterThan21_3.writeArrayValue(registerArgTypes, 0, greaterThan21_3.loadClass(Class[].class)); + ResultHandle registerLookupMethod = greaterThan21_3.invokeStaticMethod(lookupMethod, runtimeSerializationClass, + greaterThan21_3.load("register"), registerArgTypes); + ResultHandle registerArgs = greaterThan21_3.newArray(Object.class, greaterThan21_3.load(1)); + ResultHandle classesToRegister = greaterThan21_3.newArray(Class.class, greaterThan21_3.load(1)); + greaterThan21_3.writeArrayValue(classesToRegister, 0, clazz); + greaterThan21_3.writeArrayValue(registerArgs, 0, classesToRegister); + greaterThan21_3.invokeVirtualMethod(invokeMethodDescriptor, registerLookupMethod, + greaterThan21_3.loadNull(), registerArgs); + greaterThan21_3.returnValue(null); + + ResultHandle objectClass = tc.invokeStaticMethod(forNameMethodDescriptor, tc.load("java.lang.Object"), + tc.load(false), tccl); + ResultHandle serializationRegistryClass = tc.invokeStaticMethod(forNameMethodDescriptor, + tc.load("com.oracle.svm.core.jdk.serialize.SerializationRegistry"), + tc.load(false), tccl); + ResultHandle addReflectionsClass = tc.invokeStaticMethod(forNameMethodDescriptor, + tc.load("com.oracle.svm.reflect.serialize.hosted.SerializationFeature"), + tc.load(false), tccl); ResultHandle serializationSupport = tc.invokeStaticMethod( IMAGE_SINGLETONS_LOOKUP, - tc.loadClass("com.oracle.svm.core.jdk.serialize.SerializationRegistry")); + serializationRegistryClass); + + ResultHandle addReflectionsLookupArgs = tc.newArray(Class.class, tc.load(2)); + tc.writeArrayValue(addReflectionsLookupArgs, 0, tc.loadClass(Class.class)); + tc.writeArrayValue(addReflectionsLookupArgs, 1, tc.loadClass(Class.class)); + ResultHandle addReflectionsLookupMethod = tc.invokeStaticMethod(lookupMethod, addReflectionsClass, + tc.load("addReflections"), addReflectionsLookupArgs); ResultHandle reflectionFactory = tc.invokeStaticMethod( ofMethod("sun.reflect.ReflectionFactory", "getReflectionFactory", "sun.reflect.ReflectionFactory")); @@ -552,7 +592,7 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator AssignableResultHandle newSerializationConstructor = tc.createVariable(Constructor.class); ResultHandle externalizableClass = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + forNameMethodDescriptor, tc.load("java.io.Externalizable"), tc.load(false), tccl); BranchResult isExternalizable = tc @@ -562,13 +602,12 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator ResultHandle array1 = ifIsExternalizable.newArray(Class.class, tc.load(1)); ResultHandle classClass = ifIsExternalizable.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + forNameMethodDescriptor, ifIsExternalizable.load("java.lang.Class"), ifIsExternalizable.load(false), tccl); ifIsExternalizable.writeArrayValue(array1, 0, classClass); ResultHandle externalizableLookupMethod = ifIsExternalizable.invokeStaticMethod( - ofMethod("com.oracle.svm.util.ReflectionUtil", "lookupMethod", Method.class, Class.class, String.class, - Class[].class), + lookupMethod, ifIsExternalizable.loadClass(ObjectStreamClass.class), ifIsExternalizable.load("getExternalizableConstructor"), array1); @@ -576,18 +615,17 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator ifIsExternalizable.writeArrayValue(array2, 0, clazz); ResultHandle externalizableConstructor = ifIsExternalizable.invokeVirtualMethod( - ofMethod(Method.class, "invoke", Object.class, Object.class, - Object[].class), - externalizableLookupMethod, ifIsExternalizable.loadNull(), array2); + invokeMethodDescriptor, externalizableLookupMethod, ifIsExternalizable.loadNull(), array2); ResultHandle externalizableConstructorClass = ifIsExternalizable.invokeVirtualMethod( ofMethod(Constructor.class, "getDeclaringClass", Class.class), externalizableConstructor); - ifIsExternalizable.invokeStaticMethod( - ofMethod("com.oracle.svm.reflect.serialize.hosted.SerializationFeature", "addReflections", void.class, - Class.class, Class.class), - clazz, externalizableConstructorClass); + ResultHandle addReflectionsArgs1 = ifIsExternalizable.newArray(Class.class, tc.load(2)); + ifIsExternalizable.writeArrayValue(addReflectionsArgs1, 0, clazz); + ifIsExternalizable.writeArrayValue(addReflectionsArgs1, 1, externalizableConstructorClass); + ifIsExternalizable.invokeVirtualMethod(invokeMethodDescriptor, addReflectionsLookupMethod, + ifIsExternalizable.loadNull(), addReflectionsArgs1); ifIsExternalizable.returnValue(null); @@ -618,26 +656,22 @@ private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator ofMethod(Constructor.class, "getDeclaringClass", Class.class), newSerializationConstructor); - ResultHandle lookupMethod = tc.invokeStaticMethod( - ofMethod("com.oracle.svm.util.ReflectionUtil", "lookupMethod", Method.class, Class.class, String.class, - Class[].class), - tc.loadClass(Constructor.class), tc.load("getConstructorAccessor"), + ResultHandle getConstructorAccessor = tc.invokeStaticMethod( + lookupMethod, tc.loadClass(Constructor.class), tc.load("getConstructorAccessor"), tc.newArray(Class.class, tc.load(0))); ResultHandle accessor = tc.invokeVirtualMethod( - ofMethod(Method.class, "invoke", Object.class, Object.class, - Object[].class), - lookupMethod, newSerializationConstructor, + invokeMethodDescriptor, getConstructorAccessor, newSerializationConstructor, tc.newArray(Object.class, tc.load(0))); tc.invokeVirtualMethod( ofMethod("com.oracle.svm.reflect.serialize.SerializationSupport", "addConstructorAccessor", Object.class, Class.class, Class.class, Object.class), serializationSupport, clazz, newSerializationConstructorClass, accessor); - tc.invokeStaticMethod( - ofMethod("com.oracle.svm.reflect.serialize.hosted.SerializationFeature", "addReflections", void.class, - Class.class, Class.class), - clazz, objectClass); + ResultHandle addReflectionsArgs2 = tc.newArray(Class.class, tc.load(2)); + tc.writeArrayValue(addReflectionsArgs2, 0, clazz); + tc.writeArrayValue(addReflectionsArgs2, 1, objectClass); + tc.invokeVirtualMethod(invokeMethodDescriptor, addReflectionsLookupMethod, tc.loadNull(), addReflectionsArgs2); addSerializationForClass.returnValue(null); 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 c79c8285ea7c4..743d4a24155e6 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 @@ -1,6 +1,5 @@ package io.quarkus.deployment.util; -import java.lang.reflect.Array; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; @@ -377,55 +376,4 @@ public ClassNotIndexedException(DotName dotName) { } } - public static Class loadRawType(Type type) { - switch (type.kind()) { - case VOID: - return void.class; - case PRIMITIVE: - switch (type.asPrimitiveType().primitive()) { - case BOOLEAN: - return boolean.class; - case CHAR: - return char.class; - case BYTE: - return byte.class; - case SHORT: - return short.class; - case INT: - return int.class; - case LONG: - return long.class; - case FLOAT: - return float.class; - case DOUBLE: - return double.class; - default: - throw new IllegalArgumentException("Unknown primitive type: " + type); - } - case CLASS: - return load(type.asClassType().name()); - case PARAMETERIZED_TYPE: - return load(type.asParameterizedType().name()); - case ARRAY: - Class component = loadRawType(type.asArrayType().component()); - int dimensions = type.asArrayType().dimensions(); - return Array.newInstance(component, new int[dimensions]).getClass(); - case WILDCARD_TYPE: - return loadRawType(type.asWildcardType().extendsBound()); - case TYPE_VARIABLE: - return load(type.asTypeVariable().name()); - case UNRESOLVED_TYPE_VARIABLE: - return Object.class; // can't do better here - default: - throw new IllegalArgumentException("Unknown type: " + type); - } - } - - private static Class load(DotName name) { - try { - return Thread.currentThread().getContextClassLoader().loadClass(name.toString()); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java index 3b86dd1d18e84..e2b8a47b0130c 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java @@ -2,16 +2,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.InputStream; -import java.io.Serializable; import java.util.Collection; import java.util.List; import java.util.Map; -import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.Indexer; @@ -305,72 +302,6 @@ public static class MultiBoundedRepo implements Repo { public static class ErasedRepo2 extends MultiBoundedRepo { } - @Test - public void testLoadRawType() { - Index index = index(TestMethods.class); - ClassInfo clazz = index.getClassByName(DotName.createSimple(TestMethods.class.getName())); - - assertEquals(void.class, JandexUtil.loadRawType(clazz.method("aaa").returnType())); - assertEquals(int.class, JandexUtil.loadRawType(clazz.method("bbb").returnType())); - assertEquals(String.class, JandexUtil.loadRawType(clazz.method("ccc").returnType())); - assertEquals(List.class, JandexUtil.loadRawType(clazz.method("ddd").returnType())); - assertEquals(String[][].class, JandexUtil.loadRawType(clazz.method("eee").returnType())); - assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("fff").returnType())); - assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("ggg").returnType())); - assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("hhh").returnType())); - assertEquals(Comparable.class, JandexUtil.loadRawType(clazz.method("iii").returnType())); - assertEquals(Comparable.class, JandexUtil.loadRawType(clazz.method("jjj").returnType())); - assertEquals(Serializable.class, JandexUtil.loadRawType(clazz.method("kkk").returnType())); - assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("lll").returnType() - .asParameterizedType().arguments().get(0))); - assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("mmm").returnType() - .asParameterizedType().arguments().get(0))); - assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("nnn").returnType() - .asParameterizedType().arguments().get(0))); - assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("ooo").returnType() - .asParameterizedType().arguments().get(0))); - assertEquals(Number.class, JandexUtil.loadRawType(clazz.method("ppp").returnType() - .asParameterizedType().arguments().get(0))); - assertEquals(Object.class, JandexUtil.loadRawType(clazz.method("qqq").returnType() - .asParameterizedType().arguments().get(0))); - } - - public interface TestMethods { - void aaa(); - - int bbb(); - - String ccc(); - - List ddd(); - - String[][] eee(); - - X fff(); - - Y ggg(); - - > Y hhh(); - - > Y iii(); - - & Serializable> Y jjj(); - - > Y kkk(); - - List lll(); - - List mmm(); - - List nnn(); - - List ooo(); - - List ppp(); - - List qqq(); - } - private static Index index(Class... classes) { Indexer indexer = new Indexer(); for (Class clazz : classes) { diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Registry.java b/devtools/cli/src/main/java/io/quarkus/cli/Registry.java index 210a7debad06f..f88d0e04c8da3 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/Registry.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/Registry.java @@ -8,7 +8,9 @@ import picocli.CommandLine.Unmatched; @CommandLine.Command(name = "registry", sortOptions = false, mixinStandardHelpOptions = false, header = "Manage extension registries.", subcommands = { - RegistryListCommand.class }, headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "%nOptions:%n") + RegistryAddCommand.class, + RegistryListCommand.class, + RegistryRemoveCommand.class }, headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "%nOptions:%n") public class Registry extends BaseRegistryCommand { @Unmatched // avoids throwing errors for unmatched arguments diff --git a/devtools/cli/src/main/java/io/quarkus/cli/RegistryAddCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/RegistryAddCommand.java new file mode 100644 index 0000000000000..f1e6b6c31a31f --- /dev/null +++ b/devtools/cli/src/main/java/io/quarkus/cli/RegistryAddCommand.java @@ -0,0 +1,74 @@ +package io.quarkus.cli; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.stream.Collectors; + +import io.quarkus.cli.registry.BaseRegistryCommand; +import io.quarkus.cli.registry.RegistryClientMixin; +import io.quarkus.registry.config.RegistriesConfig; +import io.quarkus.registry.config.RegistriesConfigLocator; +import io.quarkus.registry.config.RegistryConfig; +import io.quarkus.registry.config.json.JsonRegistriesConfig; +import io.quarkus.registry.config.json.JsonRegistryConfig; +import io.quarkus.registry.config.json.RegistriesConfigMapperHelper; +import picocli.CommandLine; + +@CommandLine.Command(name = "add", sortOptions = false, showDefaultValues = true, mixinStandardHelpOptions = false, header = "Add a Quarkus extension registry to the registry client configuration", description = "%n" + + "This command will add a Quarkus extension registry to the registry client configuration unless it's already present", headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", parameterListHeading = "%n", optionListHeading = "Options:%n") +public class RegistryAddCommand extends BaseRegistryCommand { + + @CommandLine.Mixin + protected RegistryClientMixin registryClient; + + @CommandLine.Parameters(arity = "0..1", paramLabel = "REGISTRY-ID[,REGISTRY-ID]", description = "Registry ID to add to the registry client configuration%n" + + " Example:%n" + + " registry.quarkus.io%n" + + " registry.quarkus.acme.com,registry.quarkus.io%n") + String registryIds; + + @Override + public Integer call() throws Exception { + + registryClient.refreshRegistryCache(output); + + Path configYaml; + if (registryClient.getConfigArg() == null) { + configYaml = RegistriesConfigLocator.locateConfigYaml(); + if (configYaml == null) { + configYaml = RegistriesConfigLocator.getDefaultConfigYamlLocation(); + } + } else { + configYaml = Paths.get(registryClient.getConfigArg()); + } + + final RegistriesConfig config; + if (Files.exists(configYaml)) { + config = RegistriesConfigMapperHelper.deserialize(configYaml, JsonRegistriesConfig.class); + } else { + config = new JsonRegistriesConfig(); + } + + final Set existingIds = config.getRegistries().stream().map(RegistryConfig::getId).collect(Collectors.toSet()); + boolean persist = false; + for (String registryId : registryIds.split(",")) { + if (existingIds.add(registryId)) { + persist = true; + final JsonRegistryConfig registry = new JsonRegistryConfig(); + registry.setId(registryId); + config.getRegistries().add(registry); + output.info("Registry " + registryId + " was added"); + } else { + output.info("Registry " + registryId + " was skipped since it is already present"); + } + } + + if (persist) { + RegistriesConfigMapperHelper.serialize(config, configYaml); + } + + return CommandLine.ExitCode.OK; + } +} diff --git a/devtools/cli/src/main/java/io/quarkus/cli/RegistryListCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/RegistryListCommand.java index 52327d73034a7..96872de2fba79 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/RegistryListCommand.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/RegistryListCommand.java @@ -28,7 +28,7 @@ public class RegistryListCommand extends BaseRegistryCommand { public Integer call() throws Exception { registryClient.refreshRegistryCache(output); - final RegistriesConfig config = RegistriesConfigLocator.resolveConfig(); + final RegistriesConfig config = registryClient.resolveConfig(); final ExtensionCatalogResolver catalogResolver = streams ? registryClient.getExtensionCatalogResolver(output) : null; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/RegistryRemoveCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/RegistryRemoveCommand.java new file mode 100644 index 0000000000000..a3a3a649f19eb --- /dev/null +++ b/devtools/cli/src/main/java/io/quarkus/cli/RegistryRemoveCommand.java @@ -0,0 +1,69 @@ +package io.quarkus.cli; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.quarkus.cli.registry.BaseRegistryCommand; +import io.quarkus.cli.registry.RegistryClientMixin; +import io.quarkus.registry.config.RegistriesConfig; +import io.quarkus.registry.config.RegistriesConfigLocator; +import io.quarkus.registry.config.RegistryConfig; +import io.quarkus.registry.config.json.JsonRegistriesConfig; +import io.quarkus.registry.config.json.RegistriesConfigMapperHelper; +import picocli.CommandLine; + +@CommandLine.Command(name = "remove", sortOptions = false, showDefaultValues = true, mixinStandardHelpOptions = false, header = "Remove a Quarkus extension registry from the registry client configuration", description = "%n" + + "This command will remove a Quarkus extension registry from the registry client configuration", headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", parameterListHeading = "%n", optionListHeading = "Options:%n") +public class RegistryRemoveCommand extends BaseRegistryCommand { + + @CommandLine.Mixin + protected RegistryClientMixin registryClient; + + @CommandLine.Parameters(arity = "0..1", paramLabel = "REGISTRY-ID[,REGISTRY-ID]", description = "Registry ID to remove from the registry client configuration%n" + + " Example:%n" + + " registry.quarkus.io%n" + + " registry.quarkus.acme.com,registry.quarkus.io%n") + String registryIds; + + @Override + public Integer call() throws Exception { + + registryClient.refreshRegistryCache(output); + + Path configYaml; + if (registryClient.getConfigArg() == null) { + configYaml = RegistriesConfigLocator.locateConfigYaml(); + if (configYaml == null) { + output.error("Failed to locate the registry client configuration file"); + return CommandLine.ExitCode.SOFTWARE; + } + } else { + configYaml = Paths.get(registryClient.getConfigArg()); + } + + final RegistriesConfig config = RegistriesConfigMapperHelper.deserialize(configYaml, JsonRegistriesConfig.class); + + final Map registries = new LinkedHashMap<>(config.getRegistries().size()); + config.getRegistries().forEach(r -> registries.put(r.getId(), r)); + boolean persist = false; + for (String registryId : registryIds.split(",")) { + if (registries.remove(registryId) == null) { + output.info("Registry " + registryId + " was not previously configured"); + } else { + output.info("Registry " + registryId + " was removed"); + persist = true; + } + } + + if (persist) { + final JsonRegistriesConfig jsonConfig = new JsonRegistriesConfig(); + jsonConfig.setRegistries(new ArrayList<>(registries.values())); + RegistriesConfigMapperHelper.serialize(jsonConfig, configYaml); + } + + return CommandLine.ExitCode.OK; + } +} diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java index d95bfecfcca25..4ddad8aed716d 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java @@ -225,7 +225,9 @@ void setGradleProperties(ArrayDeque args, boolean batchMode) { } args.add(registryClient.getRegistryClientProperty()); - final String configFile = System.getProperty(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY); + final String configFile = registryClient.getConfigArg() == null + ? System.getProperty(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY) + : registryClient.getConfigArg(); if (configFile != null) { args.add("-D" + RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY + "=" + configFile); } 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 b1e1cdc55ec38..1381a83916b15 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 @@ -26,6 +26,7 @@ import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.project.buildfile.MavenProjectBuildFile; import picocli.CommandLine; @@ -75,6 +76,7 @@ public BuildTool getBuildTool() { } QuarkusProject quarkusProject() throws Exception { + QuarkusProjectHelper.setToolsConfig(registryClient.resolveConfig()); return MavenProjectBuildFile.getProject(projectRoot, output, Version::clientVersion); } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java index cdf44d7eb7233..ff1af01cbc692 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java @@ -1,6 +1,7 @@ package io.quarkus.cli.registry; import java.nio.file.Path; +import java.nio.file.Paths; import io.quarkus.cli.Version; import io.quarkus.cli.common.OutputOptionMixin; @@ -14,6 +15,8 @@ import io.quarkus.registry.ExtensionCatalogResolver; import io.quarkus.registry.RegistryResolutionException; import io.quarkus.registry.catalog.ExtensionCatalog; +import io.quarkus.registry.config.RegistriesConfig; +import io.quarkus.registry.config.RegistriesConfigLocator; import picocli.CommandLine; public class RegistryClientMixin { @@ -28,10 +31,21 @@ public final String getRegistryClientProperty() { "--refresh" }, description = "Refresh the local Quarkus extension registry cache", defaultValue = "false") boolean refresh = false; + @CommandLine.Option(paramLabel = "CONFIG", names = { "--config" }, description = "Configuration file") + String config; + public boolean enabled() { return true; } + public String getConfigArg() { + return config; + } + + public RegistriesConfig resolveConfig() { + return config == null ? RegistriesConfigLocator.resolveConfig() : RegistriesConfigLocator.load(Paths.get(config)); + } + public QuarkusProject createQuarkusProject(Path projectRoot, TargetQuarkusVersionGroup targetVersion, BuildTool buildTool, OutputOptionMixin log) throws RegistryResolutionException { ExtensionCatalog catalog = getExtensionCatalog(targetVersion, log); @@ -46,6 +60,9 @@ ExtensionCatalog getExtensionCatalog(TargetQuarkusVersionGroup targetVersion, Ou throws RegistryResolutionException { log.debug("Resolving Quarkus extension catalog for " + targetVersion); QuarkusProjectHelper.setMessageWriter(log); + if (enabled()) { + QuarkusProjectHelper.setToolsConfig(resolveConfig()); + } if (VALIDATE && targetVersion.isStreamSpecified() && !enabled()) { throw new UnsupportedOperationException( diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java index 2277ca74cbf45..ffc8addf886f5 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java @@ -19,6 +19,10 @@ import org.junit.jupiter.api.Test; import io.quarkus.devtools.testing.RegistryClientTestHelper; +import io.quarkus.registry.config.RegistriesConfig; +import io.quarkus.registry.config.RegistriesConfigLocator; +import io.quarkus.registry.config.json.JsonRegistriesConfig; +import io.quarkus.registry.config.json.RegistriesConfigMapperHelper; import picocli.CommandLine; public class CliNonProjectTest { @@ -200,6 +204,67 @@ public void testRegistryRefresh() throws Exception { "Should contain '- registry.quarkus.io', found: " + result.stdout); } + @Test + public void testRegistryAddRemove() throws Exception { + + CliDriver.Result result; + + final Path testConfigYaml = workspaceRoot.resolve("test-registry-add-remove.yaml").toAbsolutePath(); + Files.deleteIfExists(testConfigYaml); + + assertThat(testConfigYaml).doesNotExist(); + result = CliDriver.execute(workspaceRoot, "registry", "add", "one,two", "--config", testConfigYaml.toString()); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + + assertThat(testConfigYaml).exists(); + RegistriesConfig testConfig = RegistriesConfigLocator.load(testConfigYaml); + assertThat(testConfig.getRegistries()).hasSize(2); + assertThat(testConfig.getRegistries().get(0).getId()).isEqualTo("one"); + assertThat(testConfig.getRegistries().get(1).getId()).isEqualTo("two"); + + result = CliDriver.execute(workspaceRoot, "registry", "add", "two,three", "--config", testConfigYaml.toString()); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + + testConfig = RegistriesConfigLocator.load(testConfigYaml); + assertThat(testConfig.getRegistries()).hasSize(3); + assertThat(testConfig.getRegistries().get(0).getId()).isEqualTo("one"); + assertThat(testConfig.getRegistries().get(1).getId()).isEqualTo("two"); + assertThat(testConfig.getRegistries().get(2).getId()).isEqualTo("three"); + + result = CliDriver.execute(workspaceRoot, "registry", "remove", "one,two", "--config", testConfigYaml.toString()); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + + testConfig = RegistriesConfigLocator.load(testConfigYaml); + assertThat(testConfig.getRegistries()).hasSize(1); + assertThat(testConfig.getRegistries().get(0).getId()).isEqualTo("three"); + + result = CliDriver.execute(workspaceRoot, "registry", "add", "four", "--config", testConfigYaml.toString()); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + + testConfig = RegistriesConfigLocator.load(testConfigYaml); + assertThat(testConfig.getRegistries()).hasSize(2); + assertThat(testConfig.getRegistries().get(0).getId()).isEqualTo("three"); + assertThat(testConfig.getRegistries().get(1).getId()).isEqualTo("four"); + + result = CliDriver.execute(workspaceRoot, "registry", "remove", "three,four,five", "--config", + testConfigYaml.toString()); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + + testConfig = RegistriesConfigMapperHelper.deserialize(testConfigYaml, JsonRegistriesConfig.class); + assertThat(testConfig.getRegistries()).isEmpty(); + + testConfig = RegistriesConfigLocator.load(testConfigYaml); + assertThat(testConfig.getRegistries()).hasSize(1); + assertThat(testConfig.getRegistries().get(0).getId()).isEqualTo("registry.quarkus.io"); + + Files.delete(testConfigYaml); + } + private static String getRequiredProperty(String name) { return Objects.requireNonNull(System.getProperty(name)); diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index 5fda6e8e0c243..46a85356e9d69 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -632,25 +632,25 @@ For this use case, you can set the `quarkus.package.type=native-sources`. This will execute the java compilation as if you would have started native compilation (`-Pnative`), but stops before triggering the actual call to GraalVM's `native-image`. [source,bash] ---- +---- $ ./mvnw clean package -Dquarkus.package.type=native-sources ---- +---- After compilation has finished, you find the build artifact in `target/native-sources`: [source,bash] ---- +---- $ cd target/native-sources $ ls -native-image.args getting-started-1.0.0-SNAPSHOT-runner.jar ---- +native-image.args getting-started-1.0.0-SNAPSHOT-runner.jar lib +---- -You see that, beside the expected JAR, a file `native-image.args` was created. +From the output above one can see that, in addition to the produced jar file and the associated lib directory, a text file named `native-image.args` was created. This file holds all parameters (including the name of the JAR to compile) to pass along to GraalVM's `native-image` command. If you have GraalVM installed, you can start the native compilation by executing: [source,bash] ---- +---- $ cd target/native-source $ native-image $(cat native-image.args) ... @@ -659,14 +659,14 @@ native-image.args getting-started-1.0.0-SNAPSHOT-runner getting-started-1.0.0-SNAPSHOT-runner.build_artifacts.txt getting-started-1.0.0-SNAPSHOT-runner.jar ---- +---- The process for Gradle is analogous. Running the build process in a container is also possible: [source,bash,subs=attributes+] ---- +---- cd target/native-image docker run \ -it \ @@ -676,28 +676,28 @@ docker run \ --entrypoint bin/sh \ quay.io/quarkus/ubi-quarkus-native-image:{graalvm-flavor} \ <3> -c "native-image $(cat native-image.args) -J-Xmx4g" <4> ---- +---- <1> Mount the host's directory `target/native-image` to the container's `/work`. Thus, the generated binary will also be written to this directory. <2> Switch the working directory to `/work`, which we have mounted in <1>. <3> Use the `quay.io/quarkus/ubi-quarkus-native-image:{graalvm-flavor}` docker image introduced in <<#multistage-docker,Using a multi-stage Docker build>> to build the native image. -<4> Call `native-image` with the content of file `native-image.args` as arguments. We also supply an additional argument to limit the process's maximum memory limitation to 4 Gigabyte. +<4> Call `native-image` with the content of file `native-image.args` as arguments. We also supply an additional argument to limit the process's maximum memory to 4 Gigabytes (this may vary depending on the project being built and the machine building it). -In a CI/CD setup, we would: +[WARNING] +==== +If you are running on a Windows machine, please keep in mind that the binary was created within a Linux docker container. +Hence, the binary will not be executable on the host Windows machine. +==== -1. register the output of the step executing `./mvnw ...` command (i.e. directory `target/native-image`) as build artifact, -2. require this artifact in the step executing the `native-image ...` command, and -3. register the output of the step executing the `native-image ...` command (i.e. files matching `target/*runner`) as build artifact. +A high level overview of what the various steps of a CI/CD pipeline would look is the following: -The environment executing `1` only needs access to a Java- and a Maven- (or Gradle)-installation, while the environment executing `3` only needs access to a GraalVM installation (including the `native-image` feature). +1. Register the output of the step executing `./mvnw ...` command (i.e. directory `target/native-image`) as a build artifact, +2. Require this artifact in the step executing the `native-image ...` command, and +3. Register the output of the step executing the `native-image ...` command (i.e. files matching `target/*runner`) as build artifact. -The generated binary can then be used to create a container image, as explained above. +The environment executing step `1` only needs Java and Maven (or Gradle) installed, while the environment executing step `3` only needs a GraalVM installation (including the `native-image` feature). -[WARNING] -=== -If you are running on a Windows machine, please keep in mind that the binary was created within a Linux docker container. -Hence, the binary will not be executable on the host Windows machine. -=== +Depending on what the final desired output of the CI/CD pipeline is, the generated binary might then be used to create a container image. == Debugging native executable diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index 54e77e33afaaa..9261cbfac94a5 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -256,9 +256,72 @@ There are two interesting parts in this listing: <1> the client stub is injected with the `@RestClient` annotation instead of the usual CDI `@Inject` <2> the call we are making with the client is blocking, hence we need the `@Blocking` annotation on the REST endpoint +== Programmatic client creation with RestClientBuilder +Instead of annotating the client with `@RegisterRestClient`, and injecting +a client with `@RestClient`, you can also create REST Client programmatically. +You do that with `RestClientBuilder`. + +With this approach the client interface could look as follows: + +[source,java] +---- +package org.acme.rest.client; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import java.util.Set; + +@Path("/v2") +public interface CountriesService { + + @GET + @Path("/name/{region}") + Set getByRegion(String region, @QueryParam("city") String city); +} +---- + +And the service as follows: +[source,java] +---- +package org.acme.rest.client; + +import io.smallrye.common.annotation.Blocking; +import org.jboss.resteasy.reactive.RestPath; + +import javax.annotation.PostConstruct; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import java.util.Set; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; + +@Path("/country") +public class CountriesResource { + + private CountriesService countriesService; + + @PostConstruct + public void setUp(){ + countriesService = RestClientBuilder.newBuilder() + .baseUrl("https://restcountries.eu/rest") + .build(CountriesService.class); + } + + @GET + @Path("/name/{name}") + @Blocking + public Set name(String name) { + return countriesService.getByName(name); + } +} + +---- + == Update the test -We also need to update the functional test to reflect the changes made to the endpoint. +Next, we need to update the functional test to reflect the changes made to the endpoint. Edit the `src/test/java/org/acme/rest/client/CountriesResourceTest.java` file and change the content of the `testCountryNameEndpoint` method to: diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesUtil.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesUtil.java index 48de13b00794b..a6c78c961a25f 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesUtil.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesUtil.java @@ -4,6 +4,8 @@ import java.util.Optional; import java.util.function.IntFunction; +import javax.enterprise.inject.spi.DeploymentException; + import org.eclipse.microprofile.config.Config; import org.jboss.jandex.DotName; import org.jboss.jandex.ParameterizedType; @@ -42,6 +44,10 @@ static ResultHandle createReadMandatoryValueAndConvertIfNeeded(String propertyNa DotName declaringClass, BytecodeCreator bytecodeCreator, ResultHandle config) { + if (isMap(resultType)) { + throw new DeploymentException( + "Using a Map is not supported for classes annotated with '@ConfigProperties'. Consider using https://quarkus.io/guides/config-mappings instead."); + } if (isCollection(resultType)) { ResultHandle smallryeConfig = bytecodeCreator.checkCast(config, SmallRyeConfig.class); @@ -79,6 +85,10 @@ static ReadOptionalResponse createReadOptionalValueAndConvertIfNeeded(String pro BytecodeCreator bytecodeCreator, ResultHandle config) { ResultHandle optionalValue; + if (isMap(resultType)) { + throw new DeploymentException( + "Using a Map is not supported for classes annotated with '@ConfigProperties'. Consider using https://quarkus.io/guides/config-mappings instead."); + } if (isCollection(resultType)) { ResultHandle smallryeConfig = bytecodeCreator.checkCast(config, SmallRyeConfig.class); @@ -130,6 +140,11 @@ private static boolean isCollection(final Type resultType) { DotNames.SET.equals(resultType.name()); } + private static boolean isMap(final Type resultType) { + return DotNames.MAP.equals(resultType.name()) || + DotNames.HASH_MAP.equals(resultType.name()); + } + static Type determineSingleGenericType(Type type, DotName declaringClass) { if (!(type.kind() == Type.Kind.PARAMETERIZED_TYPE)) { throw new IllegalArgumentException("Type " + type.name().toString() + " which is used in class " + declaringClass diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/DotNames.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/DotNames.java index 64afa862b880a..46224bf434135 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/DotNames.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/DotNames.java @@ -1,7 +1,9 @@ package io.quarkus.arc.deployment.configproperties; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -23,6 +25,8 @@ private DotNames() { static final DotName LIST = DotName.createSimple(List.class.getName()); static final DotName SET = DotName.createSimple(Set.class.getName()); static final DotName COLLECTION = DotName.createSimple(Collection.class.getName()); + static final DotName MAP = DotName.createSimple(Map.class.getName()); + static final DotName HASH_MAP = DotName.createSimple(HashMap.class.getName()); static final DotName ENUM = DotName.createSimple(Enum.class.getName()); static final DotName MP_CONFIG_PROPERTIES = DotName .createSimple(org.eclipse.microprofile.config.inject.ConfigProperties.class.getName()); diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/configproperties/MapTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/configproperties/MapTest.java new file mode 100644 index 0000000000000..f7b3d4f83c7ec --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/configproperties/MapTest.java @@ -0,0 +1,41 @@ +package io.quarkus.arc.test.configproperties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Map; + +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.config.ConfigProperties; +import io.quarkus.test.QuarkusUnitTest; + +public class MapTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(WithMap.class)) + .assertException(e -> { + assertEquals(DeploymentException.class, e.getClass()); + assertTrue(e.getMessage().contains("config-mappings")); + }); + + @Test + public void shouldNotBeInvoked() { + // This method should not be invoked + fail(); + } + + @ConfigProperties + public static class WithMap { + + public String name; + public Map other; + } +} diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java index f1ccacd608f65..ba234d4c4ac61 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java @@ -233,9 +233,9 @@ public void openshiftBuildFromJar(OpenshiftConfig openshiftConfig, .filter(r -> r.getName().endsWith("kubernetes" + File.separator + "openshift.yml")) .findFirst(); - if (!openshiftYml.isPresent()) { + if (openshiftYml.isEmpty()) { LOG.warn( - "No Openshift manifests were generated (most likely due to the fact that the service is not an HTTP service) so no openshift process will be taking place"); + "No Openshift manifests were generated so no openshift build process will be taking place"); return; } @@ -298,9 +298,9 @@ public void openshiftBuildFromNative(OpenshiftConfig openshiftConfig, S2iConfig .filter(r -> r.getName().endsWith("kubernetes" + File.separator + "openshift.yml")) .findFirst(); - if (!openshiftYml.isPresent()) { + if (openshiftYml.isEmpty()) { LOG.warn( - "No Openshift manifests were generated (most likely due to the fact that the service is not an HTTP service) so no openshift process will be taking place"); + "No Openshift manifests were generated so no openshift build process will be taking place"); return; } //The contextRoot is where inside the tarball we will add the jars. A null value means everything will be added under '/' while "target" means everything will be added under '/target'. diff --git a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java index 2bfae9e97043e..fb9aff6596f06 100644 --- a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java +++ b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java @@ -184,9 +184,9 @@ public void s2iBuildFromJar(S2iConfig s2iConfig, ContainerImageConfig containerI .filter(r -> r.getName().endsWith("kubernetes" + File.separator + "openshift.yml")) .findFirst(); - if (!openshiftYml.isPresent()) { + if (openshiftYml.isEmpty()) { LOG.warn( - "No Openshift manifests were generated (most likely due to the fact that the service is not an HTTP service) so no s2i process will be taking place"); + "No Openshift manifests were generated so no s2i process will be taking place"); return; } @@ -224,9 +224,9 @@ public void s2iBuildFromNative(S2iConfig s2iConfig, ContainerImageConfig contain .filter(r -> r.getName().endsWith("kubernetes" + File.separator + "openshift.yml")) .findFirst(); - if (!openshiftYml.isPresent()) { + if (openshiftYml.isEmpty()) { LOG.warn( - "No Openshift manifests were generated (most likely due to the fact that the service is not an HTTP service) so no s2i process will be taking place"); + "No Openshift manifests were generated so no s2i process will be taking place"); return; } 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 6f0c9cc49a13a..3ca13e90a4354 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 @@ -153,7 +153,7 @@ public static class HibernateOrmConfigPersistenceUnitLog { * If set, Hibernate will log queries that took more than specified number of milliseconds to execute. */ @ConfigItem - public Optional queriesSlowerThanMs; + public Optional queriesSlowerThanMs = Optional.empty(); public boolean isAnyPropertySet() { return sql || !formatSql || jdbcWarnings.isPresent() || queriesSlowerThanMs.isPresent(); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java index ddf8000db0f69..141965ec47b30 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/tenant/HibernateMultiTenantConnectionProvider.java @@ -1,9 +1,12 @@ package io.quarkus.hibernate.orm.runtime.tenant; +import java.lang.annotation.Annotation; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Default; import org.hibernate.engine.jdbc.connections.spi.AbstractMultiTenantConnectionProvider; @@ -12,6 +15,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.ManagedContext; import io.quarkus.hibernate.orm.PersistenceUnit.PersistenceUnitLiteral; import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; @@ -19,7 +23,6 @@ * Maps from the Quarkus {@link TenantConnectionResolver} to the {@link HibernateMultiTenantConnectionProvider} model. * * @author Michael Schnell - * */ public final class HibernateMultiTenantConnectionProvider extends AbstractMultiTenantConnectionProvider { @@ -34,7 +37,23 @@ public HibernateMultiTenantConnectionProvider(String persistenceUnitName) { @Override protected ConnectionProvider getAnyConnectionProvider() { - String tenantId = tenantResolver(persistenceUnitName).getDefaultTenantId(); + InstanceHandle tenantResolver = tenantResolver(persistenceUnitName); + String tenantId; + // Activate RequestScope if the TenantResolver is @RequestScoped or @SessionScoped + ManagedContext requestContext = Arc.container().requestContext(); + Class tenantScope = tenantResolver.getBean().getScope(); + boolean requiresRequestScope = (tenantScope == RequestScoped.class || tenantScope == SessionScoped.class); + boolean forceRequestActivation = (!requestContext.isActive() && requiresRequestScope); + try { + if (forceRequestActivation) { + requestContext.activate(); + } + tenantId = tenantResolver.get().getDefaultTenantId(); + } finally { + if (forceRequestActivation) { + requestContext.deactivate(); + } + } if (tenantId == null) { throw new IllegalStateException("Method 'TenantResolver.getDefaultTenantId()' returned a null value. " + "This violates the contract of the interface!"); @@ -88,7 +107,7 @@ private static ConnectionProvider resolveConnectionProvider(String persistenceUn * * @return Current tenant resolver. */ - private static TenantResolver tenantResolver(String persistenceUnitName) { + private static InstanceHandle tenantResolver(String persistenceUnitName) { InstanceHandle resolverInstance; if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) { resolverInstance = Arc.container().instance(TenantResolver.class, Default.Literal.INSTANCE); @@ -102,7 +121,7 @@ private static TenantResolver tenantResolver(String persistenceUnitName) { + "You need to create an implementation for this interface to allow resolving the current tenant identifier.", TenantResolver.class.getSimpleName(), persistenceUnitName)); } - return resolverInstance.get(); + return resolverInstance; } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 76ececeee141f..7e3b6aca5fa51 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -176,8 +176,10 @@ public static List createDecorators(Optional projec result.addAll(createArgsDecorator(project, target, name, config, command)); //Handle Probes - result.addAll(createProbeDecorators(name, target, config.getLivenessProbe(), config.getReadinessProbe(), - livenessProbePath, readinessProbePath)); + if (!ports.isEmpty()) { + result.addAll(createProbeDecorators(name, target, config.getLivenessProbe(), config.getReadinessProbe(), + livenessProbePath, readinessProbePath)); + } //Handle RBAC if (!roleBindings.isEmpty()) { diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java index 8082edf501a05..0f8251b1cc906 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -96,11 +96,6 @@ public void build(ApplicationInfoBuildItem applicationInfo, List allConfigurationRegistry = new ArrayList<>(configurators); List allDecorators = new ArrayList<>(decorators); - if (kubernetesPorts.isEmpty()) { - log.debug("The service is not an HTTP service so no Kubernetes manifests will be generated"); - return; - } - final Path root; try { root = Files.createTempDirectory("quarkus-kubernetes"); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java index e1f36f3945d7a..0d9df43328048 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java @@ -198,7 +198,7 @@ public List createDecorators(ApplicationInfoBuildItem applic .findFirst().orElse(DEFAULT_HTTP_PORT); result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyHttpGetActionPortDecorator(name, name, port))); - // Hanlde non-s2i + // Handle non-s2i if (!capabilities.isPresent(Capability.CONTAINER_IMAGE_S2I) && !capabilities.isPresent("io.quarkus.openshift") && !capabilities.isPresent(Capability.CONTAINER_IMAGE_OPENSHIFT)) { diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverter.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverter.java index 7931f9a686d8b..b38b64935a840 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverter.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverter.java @@ -1,5 +1,6 @@ package io.quarkus.mongodb.runtime; +import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import java.util.HashMap; @@ -17,27 +18,30 @@ * ServiceBindingConverter for MongoDB to support SBO (Service Binding Operator) in Quarkus. * * This class supports both the Standard and SRV connection string format for - * MongoDB depending on whether port is provided or not. + * MongoDB depending on whether srv property is true or false. If the srv property is + * missing then it is same as having a value of false. * *
+ *
* Following individual properties are supported to make the connection string: *
    *
  • username
  • *
  • password
  • - *
  • host
  • - *
  • port
  • + *
  • host (port information can be provided in this property separated by : sign, e.g. localhost:27010
  • *
  • database
  • + *
  • srv
  • + *
  • options
  • *
- * Other than host all other properties are optinoal + * Other than host all other properties are optional * *
*
- * Only following options are supported by default: + * The Quarkus properties set by this class are: *
    - *
  • retryWrites=true
  • - *
  • w=majority
  • + *
  • quarkus.mongodb.connection-string
  • + *
  • quarkus.mongodb.credentials.username (if username is provided)
  • + *
  • quarkus.mongodb.credentials.password (if password is provided)
  • *
- * */ public class MongoServiceBindingConverter implements ServiceBindingConverter { private static final Logger LOGGER = Logger.getLogger(MongoServiceBindingConverter.class); @@ -45,21 +49,22 @@ public class MongoServiceBindingConverter implements ServiceBindingConverter { private static final String BINDING_TYPE = "mongodb"; public static final String BINDING_CONFIG_SOURCE_NAME = BINDING_TYPE + "-k8s-service-binding-source"; public static final String MONGO_DB_CONNECTION_STRING = "quarkus.mongodb.connection-string"; + public static final String MONGO_DB_USERNAME = "quarkus.mongodb.credentials.username"; + public static final String MONGO_DB_PASSWORD = "quarkus.mongodb.credentials.password"; // DB properties public static final String DB_USER = "username"; public static final String DB_PASSWORD = "password"; public static final String DB_HOST = "host"; - public static final String DB_PORT = "port"; public static final String DB_DATABASE = "database"; + public static final String DB_OPTIONS = "options"; public static final String DB_PREFIX_STANDARD = "mongodb://"; public static final String DB_PREFIX_SRV = "mongodb+srv://"; - public static final String DB_DEFAULT_OPTIONS = "?retryWrites=true&w=majority"; + public static final String DB_SRV = "srv"; - private static final String CONNECTION_STRING_WITH_USER = "%s%s:%s@%s/%s" + DB_DEFAULT_OPTIONS; // user:pass@hostPort/database - private static final String CONNECTION_STRING_WITH_USER_NO_DB = "%s%s:%s@%s" + DB_DEFAULT_OPTIONS; // user:pass@hostPort - private static final String CONNECTION_STRING_WITHOUT_USER = "%s%s/%s" + DB_DEFAULT_OPTIONS; // hostPort/database - private static final String CONNECTION_STRING_WITHOUT_USER_NO_DB = "%s%s" + DB_DEFAULT_OPTIONS; // hostPort + // 1st format specifier is for the prefix in each of the following ConnectionString + private static final String CONNECTION_STRING_WITH_HOST_N_DB = "%s%s/%s"; // hostPort/database + private static final String CONNECTION_STRING_WITH_ONLY_HOST = "%s%s"; // hostPort @Override public Optional convert(List serviceBindings) { @@ -68,37 +73,54 @@ public Optional convert(List service return Optional.empty(); } + ServiceBinding binding = matchingByType.get(); Map properties = new HashMap<>(); - properties.put(MONGO_DB_CONNECTION_STRING, getConnectionString(matchingByType.get())); + setConnectionString(binding, properties); + setUsername(binding, properties); + setPassword(binding, properties); return Optional.of(new ServiceBindingConfigSource(BINDING_CONFIG_SOURCE_NAME, properties)); } - private String getConnectionString(ServiceBinding binding) { - String user = getDbProperty(binding, DB_USER); - String password = getDbProperty(binding, DB_PASSWORD); - String hostPort = getHostPort(binding); + private void setConnectionString(ServiceBinding binding, Map properties) { + String hostPort = getHost(binding); + String prefix = isSrv(binding) ? DB_PREFIX_SRV : DB_PREFIX_STANDARD; String database = getDbProperty(binding, DB_DATABASE); - String prefix = isPortProvided(hostPort) ? DB_PREFIX_STANDARD : DB_PREFIX_SRV; - + String options = getDbProperty(binding, DB_OPTIONS); + boolean isOptionsNotBlank = isNotBlank(options); String connectionString; - if (isBlank(user)) { - if (isBlank(database)) { - connectionString = format(CONNECTION_STRING_WITHOUT_USER_NO_DB, prefix, hostPort); - } else { - connectionString = format(CONNECTION_STRING_WITHOUT_USER, prefix, hostPort, database); + + if (isBlank(database)) { + connectionString = format(CONNECTION_STRING_WITH_ONLY_HOST, prefix, hostPort); + + if (isOptionsNotBlank) { + // We need a trailing slash before options otherwise Mongo throws "contains options without trailing slash" error + // If the database value is not present, then we haven't yet added the trailing slash (and hence add it here) + connectionString += "/"; } } else { - if (isBlank(database)) { - connectionString = format(CONNECTION_STRING_WITH_USER_NO_DB, prefix, user, password, hostPort); - } else { - connectionString = format(CONNECTION_STRING_WITH_USER, prefix, user, password, hostPort, database); - } + connectionString = format(CONNECTION_STRING_WITH_HOST_N_DB, prefix, hostPort, database); + } + + if (isOptionsNotBlank) { + connectionString += "?" + options; } - LOGGER.info(format("MongoDB connection string: [%s]", connectionString)); - return connectionString; + LOGGER.debug(format("MongoDB connection string: [%s]", connectionString)); + + properties.put(MONGO_DB_CONNECTION_STRING, connectionString); + } + + private void setUsername(ServiceBinding binding, Map properties) { + String username = getDbProperty(binding, DB_USER); + LOGGER.debug(format("MongoDB username=%s", username)); + + properties.put(MONGO_DB_USERNAME, username); + } + + private void setPassword(ServiceBinding binding, Map properties) { + properties.put(MONGO_DB_PASSWORD, getDbProperty(binding, DB_PASSWORD)); } private String getDbProperty(ServiceBinding binding, String dbPropertyKey) { @@ -110,24 +132,20 @@ private String getDbProperty(ServiceBinding binding, String dbPropertyKey) { return dbPropertyValue; } - private String getHostPort(ServiceBinding binding) { + private String getHost(ServiceBinding binding) { String host = getDbProperty(binding, DB_HOST); - String port = getDbProperty(binding, DB_PORT); - String hostPort = host; - if (isNotBlank(host)) { - if (isNotBlank(port)) { - hostPort = host + ":" + port; - } - } else { + if (isBlank(host)) { LOGGER.warn("Unable to get the host property. Connection string won't be correct"); } - return hostPort; + return host; } - private boolean isPortProvided(String hostPort) { - return hostPort != null && hostPort.contains(":"); + private boolean isSrv(ServiceBinding binding) { + String srv = getDbProperty(binding, DB_SRV); + + return isNotBlank(srv) && parseBoolean(srv); } private boolean isBlank(String value) { diff --git a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverterTest.java b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverterTest.java index db650ef0a3477..36370ed9e2f8e 100644 --- a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverterTest.java +++ b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/runtime/MongoServiceBindingConverterTest.java @@ -2,14 +2,16 @@ import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.BINDING_CONFIG_SOURCE_NAME; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_DATABASE; -import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_DEFAULT_OPTIONS; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_HOST; +import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_OPTIONS; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_PASSWORD; -import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_PORT; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_PREFIX_SRV; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_PREFIX_STANDARD; +import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_SRV; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.DB_USER; import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.MONGO_DB_CONNECTION_STRING; +import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.MONGO_DB_PASSWORD; +import static io.quarkus.mongodb.runtime.MongoServiceBindingConverter.MONGO_DB_USERNAME; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -32,11 +34,19 @@ public class MongoServiceBindingConverterTest { private final static String BINDING_DIRECTORY_NO_PORT = "no-port"; private final static String BINDING_DIRECTORY_NO_USER = "no-user"; + private static final String DB_PORT = "port"; + private static final String EXPECTED_USERNAME = "someUserName"; - private static final String EXPECTED_PASSWORD = "password123 isItAGoodPassword"; + private static final String EXPECTED_PASSWORD = "password123 isItAGoodPassword 7@%|?^B6"; private static final String EXPECTED_HOST = "mongodb0.example.com"; + private static final String EXPECTED_STANDARD_HOST_N_PORT_2 = "mongodb0.example.com:27017, mongodb1.example.com:27017, mongodb2.example.com:27017"; private static final String EXPECTED_DB = "random-DB"; private static final String EXPECTED_PORT = "11010"; + private static final String EXPECTED_HOST_N_PORT = EXPECTED_HOST + ":" + EXPECTED_PORT; + private static final String DB_N_OPTIONS_SEPARATOR = "/"; + private static final String OPTIONS_SEPARATOR = "?"; + private static final String EXPECTED_OPTIONS = "retryWrites=true&w=majority"; + private static final String EXPECTED_SRV = "true"; @Test public void testBindingWithAllProperties() { @@ -47,9 +57,10 @@ public void testBindingWithAllProperties() { assertThat(serviceBinding.getProvider()).isEqualTo("atlas"); assertThat(serviceBinding.getProperties().get(DB_USER)).isEqualTo(EXPECTED_USERNAME); assertThat(serviceBinding.getProperties().get(DB_PASSWORD)).isEqualTo(EXPECTED_PASSWORD); - assertThat(serviceBinding.getProperties().get(DB_HOST)).isEqualTo(EXPECTED_HOST); - assertThat(serviceBinding.getProperties().get(DB_PORT)).isEqualTo(EXPECTED_PORT); + assertThat(serviceBinding.getProperties().get(DB_HOST)).isEqualTo(EXPECTED_HOST_N_PORT); + assertThat(serviceBinding.getProperties().get(DB_PORT)).isNull(); assertThat(serviceBinding.getProperties().get(DB_DATABASE)).isEqualTo(EXPECTED_DB); + assertThat(serviceBinding.getProperties().get(DB_OPTIONS)).isEqualTo(EXPECTED_OPTIONS); } @Test @@ -64,100 +75,190 @@ public void testBindingWithMissingProperties() { assertThat(serviceBinding.getProperties().get(DB_HOST)).isEqualTo(EXPECTED_HOST); assertThat(serviceBinding.getProperties().get(DB_PORT)).isNull(); assertThat(serviceBinding.getProperties().get(DB_DATABASE)).isEqualTo(EXPECTED_DB); + assertThat(serviceBinding.getProperties().get(DB_OPTIONS)).isEqualTo(EXPECTED_OPTIONS); } @Test public void testConnectionStringWithPortPresent() { - ServiceBindingConfigSource serviceBindingConfigSource = readBindingAndVerifyConfigSource(BINDING_DIRECTORY_ALL_PROPS); + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); + properties.remove(DB_SRV); // Make it a Standard connection string - String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_USERNAME + ":" + EXPECTED_PASSWORD + - "@" + EXPECTED_HOST + ":" + EXPECTED_PORT + "/" + EXPECTED_DB + DB_DEFAULT_OPTIONS; + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( + BINDING_DIRECTORY_ALL_PROPS, properties); + + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST_N_PORT + DB_N_OPTIONS_SEPARATOR + EXPECTED_DB + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); } @Test - public void testConnectionStringWithPortMissing() { - ServiceBindingConfigSource serviceBindingConfigSource = readBindingAndVerifyConfigSource(BINDING_DIRECTORY_NO_PORT); + public void testSrvConnectionStringWithPortMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST); + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource(BINDING_DIRECTORY_NO_PORT, + properties); - String expectedConnString = DB_PREFIX_SRV + EXPECTED_USERNAME + ":" + EXPECTED_PASSWORD + - "@" + EXPECTED_HOST + "/" + EXPECTED_DB + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + DB_N_OPTIONS_SEPARATOR + EXPECTED_DB + OPTIONS_SEPARATOR + + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); + } + + @Test + public void testStandardConnectionStringWithPortMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST); + properties.remove(DB_SRV); // Make it a Standard connection string + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource(BINDING_DIRECTORY_NO_PORT, + properties); + + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST + DB_N_OPTIONS_SEPARATOR + EXPECTED_DB + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; + verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); + } + + @Test + public void testStandardConnectionStringWithMultipleHostAndPort() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_STANDARD_HOST_N_PORT_2); + properties.remove(DB_SRV); // Make it a Standard connection string + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource(BINDING_DIRECTORY_NO_PORT, + properties); + + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_STANDARD_HOST_N_PORT_2 + DB_N_OPTIONS_SEPARATOR + EXPECTED_DB + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; + verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); } @Test public void testConnectionStringWithPortAndDatabaseMissing() { - HashMap properties = getMapWithAllValuesPopulated(); - properties.remove(DB_PORT); // BindingDirectory doesn't have this property, but removing it since it's put back in the map + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST); properties.remove(DB_DATABASE); // This is what we actually want to test out ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( BINDING_DIRECTORY_NO_PORT, properties); - String expectedConnString = DB_PREFIX_SRV + EXPECTED_USERNAME + ":" + EXPECTED_PASSWORD + - "@" + EXPECTED_HOST + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + DB_N_OPTIONS_SEPARATOR + OPTIONS_SEPARATOR + + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); } @Test - public void testConnectionStringWithUserMissing() { + public void testConnectionStringWithUserAndOptionsMissing() { ServiceBindingConfigSource serviceBindingConfigSource = readBindingAndVerifyConfigSource(BINDING_DIRECTORY_NO_USER); - String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST + ":" + EXPECTED_PORT + "/" + EXPECTED_DB - + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST + ":" + EXPECTED_PORT + DB_N_OPTIONS_SEPARATOR + + EXPECTED_DB; + verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); + } + + @Test + public void testConnectionStringWithUserMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); + properties.remove(DB_SRV); // BindingDirectory doesn't have this property, but removing it since it's put back in the map + properties.remove(DB_USER); // This is what we actually want to test out + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource(BINDING_DIRECTORY_NO_USER, + properties); + + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST + ":" + EXPECTED_PORT + DB_N_OPTIONS_SEPARATOR + + EXPECTED_DB + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); } @Test public void testConnectionStringWithUserAndPortMissing() { - HashMap properties = getMapWithAllValuesPopulated(); + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST); properties.remove(DB_USER); // BindingDirectory doesn't have this property, but removing it since it's put back in the map - properties.remove(DB_PORT); // This is what we actually want to test out ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( BINDING_DIRECTORY_NO_USER, properties); - String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + "/" + EXPECTED_DB + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + DB_N_OPTIONS_SEPARATOR + EXPECTED_DB + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); } @Test public void testConnectionStringWithUserAndPortAndDatabaseMissing() { - HashMap properties = getMapWithAllValuesPopulated(); + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST); properties.remove(DB_USER); // BindingDirectory doesn't have this property, but removing it since it's put back in the map - properties.remove(DB_PORT); // This is what we actually want to test out properties.remove(DB_DATABASE); // This is what we actually want to test out ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( BINDING_DIRECTORY_NO_USER, properties); - String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST + DB_N_OPTIONS_SEPARATOR + OPTIONS_SEPARATOR + + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); } @Test - public void testConnectionStringWithUserAndDatabaseMissing() { - HashMap properties = getMapWithAllValuesPopulated(); + public void testStandardConnectionStringWithUserAndDatabaseMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); properties.remove(DB_USER); // BindingDirectory doesn't have this property, but removing it since it's put back in the map + properties.remove(DB_SRV); properties.remove(DB_DATABASE); // This is what we actually want to test out ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( BINDING_DIRECTORY_NO_USER, properties); - String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST + ":" + EXPECTED_PORT + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_HOST_N_PORT + DB_N_OPTIONS_SEPARATOR + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); } @Test - public void testConnectionStringWithDatabaseMissing() { - HashMap properties = getMapWithAllValuesPopulated(); + public void testSrvConnectionStringWithUserAndDatabaseMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); + properties.remove(DB_USER); // BindingDirectory doesn't have this property, but removing it since it's put back in the map + properties.remove(DB_DATABASE); // This is what we actually want to test out + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( + BINDING_DIRECTORY_NO_USER, properties); + + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST_N_PORT + DB_N_OPTIONS_SEPARATOR + + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; + verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); + } + + @Test + public void testStandardConnectionStringWithDatabaseMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); + properties.remove(DB_SRV); // BindingDirectory doesn't have this property, but removing it since it's put back in the map properties.remove(DB_DATABASE); // This is what we actually want to test out ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( BINDING_DIRECTORY_ALL_PROPS, properties); - String expectedConnString = DB_PREFIX_STANDARD + EXPECTED_USERNAME + ":" + EXPECTED_PASSWORD + - "@" + EXPECTED_HOST + ":" + EXPECTED_PORT + DB_DEFAULT_OPTIONS; + String expectedConnString = DB_PREFIX_STANDARD + + EXPECTED_HOST_N_PORT + DB_N_OPTIONS_SEPARATOR + OPTIONS_SEPARATOR + EXPECTED_OPTIONS; verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_STANDARD, expectedConnString); + + verifyUsernameAndPassword(serviceBindingConfigSource); + } + + private void verifyUsernameAndPassword(ServiceBindingConfigSource serviceBindingConfigSource) { + assertThat(serviceBindingConfigSource.getProperties().get(MONGO_DB_USERNAME)).isEqualTo(EXPECTED_USERNAME); + assertThat(serviceBindingConfigSource.getProperties().get(MONGO_DB_PASSWORD)).isEqualTo(EXPECTED_PASSWORD); + } + + @Test + public void testSrvConnectionStringWithDatabaseMissing() { + HashMap properties = getMapWithAllValuesPopulated(EXPECTED_HOST_N_PORT); + properties.remove(DB_DATABASE); // This is what we actually want to test out + + ServiceBindingConfigSource serviceBindingConfigSource = readBindingAsSpyAndVerifyConfigSource( + BINDING_DIRECTORY_ALL_PROPS, properties); + + String expectedConnString = DB_PREFIX_SRV + EXPECTED_HOST_N_PORT + DB_N_OPTIONS_SEPARATOR + OPTIONS_SEPARATOR + + EXPECTED_OPTIONS; + verifyConnectionString(serviceBindingConfigSource, DB_PREFIX_SRV, expectedConnString); + verifyUsernameAndPassword(serviceBindingConfigSource); } private ServiceBindingConfigSource readBindingAndVerifyConfigSource(String bindingDirectory) { @@ -200,13 +301,14 @@ private void verifyConnectionString(ServiceBindingConfigSource serviceBindingCon assertThat(serviceBindingConfigSource.getProperties().get(MONGO_DB_CONNECTION_STRING)).isEqualTo(expectedConnString); } - private HashMap getMapWithAllValuesPopulated() { + private HashMap getMapWithAllValuesPopulated(String host) { HashMap map = new HashMap<>(); map.put(DB_USER, EXPECTED_USERNAME); map.put(DB_PASSWORD, EXPECTED_PASSWORD); - map.put(DB_HOST, EXPECTED_HOST); - map.put(DB_PORT, EXPECTED_PORT); + map.put(DB_HOST, host); map.put(DB_DATABASE, EXPECTED_DB); + map.put(DB_OPTIONS, EXPECTED_OPTIONS); + map.put(DB_SRV, EXPECTED_SRV); return map; } diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/host b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/host index bee4cd9950942..f5ed8d222efbd 100644 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/host +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/host @@ -1 +1 @@ -mongodb0.example.com \ No newline at end of file +mongodb0.example.com:11010 \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/options b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/options new file mode 100644 index 0000000000000..92d1438443247 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/options @@ -0,0 +1 @@ +retryWrites=true&w=majority \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/password b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/password index b4a5888fa99a2..fc237f1514422 100644 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/password +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/password @@ -1 +1 @@ -password123 isItAGoodPassword \ No newline at end of file +password123 isItAGoodPassword 7@%|?^B6 \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/port b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/port deleted file mode 100644 index d0c0a5740ae48..0000000000000 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/port +++ /dev/null @@ -1 +0,0 @@ -11010 \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/srv b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/srv new file mode 100644 index 0000000000000..f32a5804e292d --- /dev/null +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/all-props/srv @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/options b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/options new file mode 100644 index 0000000000000..92d1438443247 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/options @@ -0,0 +1 @@ +retryWrites=true&w=majority \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/password b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/password index b4a5888fa99a2..fc237f1514422 100644 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/password +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/password @@ -1 +1 @@ -password123 isItAGoodPassword \ No newline at end of file +password123 isItAGoodPassword 7@%|?^B6 \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/srv b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/srv new file mode 100644 index 0000000000000..f32a5804e292d --- /dev/null +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-port/srv @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/host b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/host index bee4cd9950942..f5ed8d222efbd 100644 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/host +++ b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/host @@ -1 +1 @@ -mongodb0.example.com \ No newline at end of file +mongodb0.example.com:11010 \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/port b/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/port deleted file mode 100644 index d0c0a5740ae48..0000000000000 --- a/extensions/mongodb-client/runtime/src/test/resources/service-binding/no-user/port +++ /dev/null @@ -1 +0,0 @@ -11010 \ No newline at end of file diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/pom.xml b/extensions/panache/quarkus-resteasy-reactive-problem/deployment/pom.xml deleted file mode 100644 index 0ded82a136e8d..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - 4.0.0 - - io.quarkus - quarkus-resteasy-reactive-problem-parent - 999-SNAPSHOT - - quarkus-resteasy-reactive-problem-deployment - Quarkus Resteasy Reactive Problem - Deployment - - - io.quarkus - quarkus-arc-deployment - - - io.quarkus - quarkus-resteasy-reactive-problem - ${project.version} - - - io.quarkus - quarkus-junit5-internal - test - - - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${quarkus.version} - - - - - - - diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java b/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java deleted file mode 100644 index 2bb609980e13e..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.deployment; - -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; - -class QuarkusResteasyReactiveProblemProcessor { - - private static final String FEATURE = "quarkus-resteasy-reactive-problem"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java b/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java deleted file mode 100644 index c754355c39254..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.test; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusDevModeTest; - -public class QuarkusResteasyReactiveProblemDevModeTest { - - // Start hot reload (DevMode) test with your extension loaded - @RegisterExtension - static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); - - @Test - public void writeYourOwnDevModeTest() { - // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information - Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName()); - } -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java b/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java deleted file mode 100644 index 4056ed49eb8d7..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.test; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -public class QuarkusResteasyReactiveProblemTest { - - // Start unit test with your extension loaded - @RegisterExtension - static final QuarkusUnitTest unitTest = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); - - @Test - public void writeYourOwnUnitTest() { - // Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information - Assertions.assertTrue(true, "Add some assertions to " + getClass().getName()); - } -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/pom.xml b/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/pom.xml deleted file mode 100644 index 013cbbf231fd3..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/pom.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - 4.0.0 - - io.quarkus - quarkus-resteasy-reactive-problem-parent - 999-SNAPSHOT - - quarkus-resteasy-reactive-problem-integration-tests - Quarkus Resteasy Reactive Problem - Integration Tests - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-resteasy-reactive-problem - ${project.version} - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - io.quarkus - quarkus-maven-plugin - - - - build - - - - - - - - - native-image - - - native - - - - - - maven-surefire-plugin - - ${native.surefire.skip} - - - - maven-failsafe-plugin - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - - - native - - - - diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/main/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResource.java b/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/main/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResource.java deleted file mode 100644 index 6b2414a142218..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/main/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResource.java +++ /dev/null @@ -1,32 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package io.quarkus.resteasy.reactive.problem.it; - -import javax.enterprise.context.ApplicationScoped; -import javax.ws.rs.GET; -import javax.ws.rs.Path; - -@Path("/quarkus-resteasy-reactive-problem") -@ApplicationScoped -public class QuarkusResteasyReactiveProblemResource { - // add some rest methods here - - @GET - public String hello() { - return "Hello quarkus-resteasy-reactive-problem"; - } -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/main/resources/application.properties b/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/main/resources/application.properties deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/NativeQuarkusResteasyReactiveProblemResourceIT.java b/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/NativeQuarkusResteasyReactiveProblemResourceIT.java deleted file mode 100644 index d9a13937ccab1..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/NativeQuarkusResteasyReactiveProblemResourceIT.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.it; - -import io.quarkus.test.junit.NativeImageTest; - -@NativeImageTest -public class NativeQuarkusResteasyReactiveProblemResourceIT extends QuarkusResteasyReactiveProblemResourceTest { -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResourceTest.java b/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResourceTest.java deleted file mode 100644 index d40f4f040afa5..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/integration-tests/src/test/java/io/quarkus/resteasy/reactive/problem/it/QuarkusResteasyReactiveProblemResourceTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.it; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -public class QuarkusResteasyReactiveProblemResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/quarkus-resteasy-reactive-problem") - .then() - .statusCode(200) - .body(is("Hello quarkus-resteasy-reactive-problem")); - } -} diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/pom.xml b/extensions/panache/quarkus-resteasy-reactive-problem/pom.xml deleted file mode 100644 index 023572f12258d..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/pom.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - io.quarkus - quarkus-resteasy-reactive-problem-parent - 999-SNAPSHOT - pom - Quarkus Resteasy Reactive Problem - Parent - - deployment - runtime - - - 3.8.1 - ${surefire-plugin.version} - true - 11 - 11 - UTF-8 - UTF-8 - 999-SNAPSHOT - 3.0.0-M5 - - - - - 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} - - - - - diff --git a/extensions/panache/quarkus-resteasy-reactive-problem/runtime/pom.xml b/extensions/panache/quarkus-resteasy-reactive-problem/runtime/pom.xml deleted file mode 100644 index 7f9dc57de0c0e..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/runtime/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - io.quarkus - quarkus-resteasy-reactive-problem-parent - 999-SNAPSHOT - - quarkus-resteasy-reactive-problem - Quarkus Resteasy Reactive Problem - Runtime - - - io.quarkus - quarkus-arc - - - - - - io.quarkus - quarkus-bootstrap-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/extensions/panache/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/panache/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index baeb269fec9e5..0000000000000 --- a/extensions/panache/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Quarkus Resteasy Reactive Problem -artifact: ${project.groupId}:${project.artifactId}:${project.version} -#description: Quarkus Resteasy Reactive Problem ... -metadata: -# keywords: -# - quarkus-resteasy-reactive-problem -# guide: ... -# categories: -# - "miscellaneous" -# status: "preview" \ No newline at end of file diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java index 52a73e09abfeb..f0edb1dc23934 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/DevServicesRedisProcessor.java @@ -55,8 +55,9 @@ public class DevServicesRedisProcessor { private static volatile List closeables; private static volatile Map capturedDevServicesConfiguration; private static volatile boolean first = true; + private static volatile Boolean dockerRunning = null; - @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { IsDockerRunningSilent.class, GlobalDevServicesConfig.Enabled.class }) + @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class }) public void startRedisContainers(LaunchModeBuildItem launchMode, Optional devServicesSharedNetworkBuildItem, BuildProducer devConfigProducer, RedisBuildTimeConfig config) { @@ -102,6 +103,7 @@ public void startRedisContainers(LaunchModeBuildItem launchMode, if (first) { first = false; Runnable closeTask = () -> { + dockerRunning = null; if (closeables != null) { for (Closeable closeable : closeables) { try { @@ -138,6 +140,17 @@ private StartResult startContainer(String connectionName, DevServicesConfig devS return null; } + if (dockerRunning == null) { + dockerRunning = new IsDockerRunningSilent().getAsBoolean(); + } + + if (!dockerRunning) { + log.warn("Please configure quarkus.redis.hosts for " + + (isDefault(connectionName) ? "default redis client" : connectionName) + + " or get a working docker instance"); + return null; + } + DockerImageName dockerImageName = DockerImageName.parse(devServicesConfig.imageName.orElse(REDIS_6_ALPINE)) .asCompatibleSubstituteFor(REDIS_6_ALPINE); diff --git a/extensions/resteasy-classic/rest-client/runtime/pom.xml b/extensions/resteasy-classic/rest-client/runtime/pom.xml index 847ef56932062..e0013b0aead77 100644 --- a/extensions/resteasy-classic/rest-client/runtime/pom.xml +++ b/extensions/resteasy-classic/rest-client/runtime/pom.xml @@ -40,6 +40,10 @@ jboss-interceptors-api_1.2_spec + + org.jboss.weld + weld-api + diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml index f9cb2fc03c855..4c90724048e52 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: - "resteasy-reactive" categories: - "web" - status: "preview" + status: "stable" diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 9e0d5e8a23569..3ebca702c3420 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -14,7 +14,7 @@ metadata: categories: - "web" - "reactive" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 8e1342901d522..54c5e88b25bcd 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -13,7 +13,7 @@ metadata: categories: - "web" - "reactive" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/deployment/src/main/java/io/quarkus/resteasy/reactive/kotlin/deployment/KotlinCoroutineIntegrationProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/deployment/src/main/java/io/quarkus/resteasy/reactive/kotlin/deployment/KotlinCoroutineIntegrationProcessor.java index 9029e57348e24..922314bd4d383 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/deployment/src/main/java/io/quarkus/resteasy/reactive/kotlin/deployment/KotlinCoroutineIntegrationProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/deployment/src/main/java/io/quarkus/resteasy/reactive/kotlin/deployment/KotlinCoroutineIntegrationProcessor.java @@ -55,6 +55,7 @@ void produceCoroutineScope(BuildProducer buildItemBuild @BuildStep MethodScannerBuildItem scanner() { return new MethodScannerBuildItem(new MethodScanner() { + @SuppressWarnings("unchecked") @Override public List scan(MethodInfo method, ClassInfo actualEndpointClass, Map methodContext) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 75be33393e654..65be917a6117f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,7 +9,7 @@ metadata: categories: - "web" - "reactive" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/pom.xml deleted file mode 100644 index ad5e69f0c2e87..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 4.0.0 - - quarkus-resteasy-reactive-problem-parent - io.quarkus - 999-SNAPSHOT - - - quarkus-resteasy-reactive-problem-deployment - Quarkus - RESTEasy Reactive - Problem - Deployment - - - - io.quarkus - quarkus-resteasy-reactive-problem - ${project.version} - - - io.quarkus - quarkus-arc-deployment - - - io.quarkus - quarkus-resteasy-reactive-common-deployment - - - io.quarkus - quarkus-junit5-internal - test - - - - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - - diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java deleted file mode 100644 index 2bb609980e13e..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/main/java/io/quarkus/resteasy/reactive/problem/deployment/QuarkusResteasyReactiveProblemProcessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.deployment; - -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.FeatureBuildItem; - -class QuarkusResteasyReactiveProblemProcessor { - - private static final String FEATURE = "quarkus-resteasy-reactive-problem"; - - @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } -} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java deleted file mode 100644 index 25fdef13680e7..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemDevModeTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.test; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusDevModeTest; - -public class QuarkusResteasyReactiveProblemDevModeTest { - - // Start hot reload (DevMode) test with your extension loaded - @RegisterExtension - static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); - - @Test - public void writeYourOwnDevModeTest() { - // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information - Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName()); - } -} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java deleted file mode 100644 index 9bb7430c84a5a..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/deployment/src/test/java/io/quarkus/resteasy/reactive/problem/test/QuarkusResteasyReactiveProblemTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.quarkus.resteasy.reactive.problem.test; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -public class QuarkusResteasyReactiveProblemTest { - - // Start unit test with your extension loaded - @RegisterExtension - static final QuarkusUnitTest unitTest = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); - - @Test - public void writeYourOwnUnitTest() { - // Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information - Assertions.assertTrue(true, "Add some assertions to " + getClass().getName()); - } -} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/pom.xml deleted file mode 100644 index 7c403ffc4a768..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - quarkus-resteasy-reactive-parent-aggregator - io.quarkus - 999-SNAPSHOT - ../pom.xml - - 4.0.0 - - quarkus-resteasy-reactive-problem-parent - Quarkus - RESTEasy Reactive - Problem - pom - - - deployment - runtime - - diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/pom.xml deleted file mode 100644 index ace7323e3c26d..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 4.0.0 - - quarkus-resteasy-reactive-problem-parent - io.quarkus - 999-SNAPSHOT - - - quarkus-resteasy-reactive-problem - Quarkus - RESTEasy Reactive - Problem - Runtime - - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-resteasy-reactive-common - - - - - - - io.quarkus - quarkus-bootstrap-maven-plugin - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - - diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index baeb269fec9e5..0000000000000 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-problem/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Quarkus Resteasy Reactive Problem -artifact: ${project.groupId}:${project.artifactId}:${project.version} -#description: Quarkus Resteasy Reactive Problem ... -metadata: -# keywords: -# - quarkus-resteasy-reactive-problem -# guide: ... -# categories: -# - "miscellaneous" -# status: "preview" \ No newline at end of file diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 5e0f08af91a3c..bdcaec7612cda 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,7 +8,7 @@ metadata: categories: - "web" - "reactive" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java index e68df66ccf8a1..f8f54895427da 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java @@ -201,7 +201,7 @@ public String getRequestMethod() { @Override public String getRequestNormalisedPath() { - return context.normalisedPath(); + return context.normalizedPath(); } @Override @@ -300,6 +300,7 @@ public ServerHttpResponse setReadListener(ReadCallback callback) { return this; } + @SuppressWarnings("unchecked") @Override public T unwrap(Class theType) { if (theType == RoutingContext.class) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 13577f5aff47b..d9b37a93faab0 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -72,6 +72,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.ClientProxyUnwrapper; import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; @@ -80,6 +81,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; @@ -101,6 +103,7 @@ import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; +import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveServerRuntimeConfig; import io.quarkus.resteasy.reactive.server.runtime.ServerVertxAsyncFileMessageBodyWriter; import io.quarkus.resteasy.reactive.server.runtime.ServerVertxBufferMessageBodyWriter; import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationCompletionExceptionMapper; @@ -108,6 +111,7 @@ import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationRedirectExceptionMapper; import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.ForbiddenExceptionMapper; import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.UnauthorizedExceptionMapper; +import io.quarkus.resteasy.reactive.server.runtime.security.EagerSecurityHandler; import io.quarkus.resteasy.reactive.server.runtime.security.SecurityContextOverrideHandler; import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; import io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem; @@ -243,6 +247,7 @@ public void unremoveableBeans(Optional resource unremoveableBeans.produce(UnremovableBeanBuildItem.beanClassNames(beanParams.toArray(new String[0]))); } + @SuppressWarnings("unchecked") @BuildStep @Record(ExecutionTime.STATIC_INIT) public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, @@ -544,6 +549,8 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an .setDynamicFeatures(dynamicFeats) .setSerialisers(serialisers) .setApplicationPath(applicationPath) + .setGlobalHandlerCustomers( + new ArrayList<>(Collections.singletonList(new SecurityContextOverrideHandler.Customizer()))) //TODO: should be pluggable .setResourceClasses(resourceClasses) .setLocatableResourceClasses(subResourceClasses) .setParamConverterProviders(paramConverterProviders); @@ -599,11 +606,11 @@ private T getEffectivePropertyValue(String legacyPropertyName, T newProperty @Record(ExecutionTime.RUNTIME_INIT) public void applyRuntimeConfig(ResteasyReactiveRuntimeRecorder recorder, Optional deployment, - HttpConfiguration httpConfiguration) { + HttpConfiguration httpConf, ResteasyReactiveServerRuntimeConfig resteasyReactiveServerRuntimeConf) { if (!deployment.isPresent()) { return; } - recorder.configure(deployment.get().getDeployment(), httpConfiguration); + recorder.configure(deployment.get().getDeployment(), httpConf, resteasyReactiveServerRuntimeConf); } @BuildStep @@ -632,12 +639,26 @@ public void securityExceptionMappers(BuildProducer exc } @BuildStep - MethodScannerBuildItem integrateSecurityOverrideSupport() { + MethodScannerBuildItem integrateEagerSecurity(Capabilities capabilities, CombinedIndexBuildItem indexBuildItem) { + if (!capabilities.isPresent(Capability.SECURITY)) { + return null; + } + var index = indexBuildItem.getComputingIndex(); return new MethodScannerBuildItem(new MethodScanner() { @Override public List scan(MethodInfo method, ClassInfo actualEndpointClass, Map methodContext) { - return Collections.singletonList(new SecurityContextOverrideHandler.Customizer()); + if (SecurityTransformerUtils.hasStandardSecurityAnnotation(method)) { + return Collections.singletonList(new EagerSecurityHandler.Customizer()); + } + ClassInfo c = actualEndpointClass; + while (c.superName() != null) { + if (SecurityTransformerUtils.hasStandardSecurityAnnotation(c)) { + return Collections.singletonList(new EagerSecurityHandler.Customizer()); + } + c = index.getClassByName(c.superName()); + } + return Collections.emptyList(); } }); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java index cd937d6ce8da1..5be00deb77e53 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java @@ -96,6 +96,7 @@ public void accept(ResourceInterceptors interceptors) { }); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @BuildStep public ExceptionMappersBuildItem scanForExceptionMappers(CombinedIndexBuildItem combinedIndexBuildItem, ApplicationResultBuildItem applicationResultBuildItem, @@ -202,6 +203,7 @@ public void scanForFeatures(CombinedIndexBuildItem combinedIndexBuildItem, } } + @SuppressWarnings({ "unchecked", "rawtypes" }) @BuildStep public ContextResolversBuildItem scanForContextResolvers(CombinedIndexBuildItem combinedIndexBuildItem, ApplicationResultBuildItem applicationResultBuildItem, diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/SecurityTransformerUtils.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/SecurityTransformerUtils.java new file mode 100644 index 0000000000000..9a9ef2d9c0602 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/SecurityTransformerUtils.java @@ -0,0 +1,64 @@ +package io.quarkus.resteasy.reactive.server.deployment; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.security.Authenticated; + +public class SecurityTransformerUtils { + public static final Set SECURITY_BINDINGS = new HashSet<>(); + + static { + // keep the contents the same as in io.quarkus.resteasy.deployment.SecurityTransformerUtils + SECURITY_BINDINGS.add(DotName.createSimple(RolesAllowed.class.getName())); + SECURITY_BINDINGS.add(DotName.createSimple(Authenticated.class.getName())); + SECURITY_BINDINGS.add(DotName.createSimple(DenyAll.class.getName())); + SECURITY_BINDINGS.add(DotName.createSimple(PermitAll.class.getName())); + } + + public static boolean hasStandardSecurityAnnotation(MethodInfo methodInfo) { + return hasStandardSecurityAnnotation(methodInfo.annotations()); + } + + public static boolean hasStandardSecurityAnnotation(ClassInfo classInfo) { + return hasStandardSecurityAnnotation(classInfo.classAnnotations()); + } + + private static boolean hasStandardSecurityAnnotation(Collection instances) { + for (AnnotationInstance instance : instances) { + if (SECURITY_BINDINGS.contains(instance.name())) { + return true; + } + } + return false; + } + + public static Optional findFirstStandardSecurityAnnotation(MethodInfo methodInfo) { + return findFirstStandardSecurityAnnotation(methodInfo.annotations()); + } + + public static Optional findFirstStandardSecurityAnnotation(ClassInfo classInfo) { + return findFirstStandardSecurityAnnotation(classInfo.classAnnotations()); + } + + private static Optional findFirstStandardSecurityAnnotation(Collection instances) { + for (AnnotationInstance instance : instances) { + if (SECURITY_BINDINGS.contains(instance.name())) { + return Optional.of(instance); + } + } + return Optional.empty(); + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnApplicationTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnApplicationTest.java new file mode 100644 index 0000000000000..dffe8aca18275 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnApplicationTest.java @@ -0,0 +1,53 @@ +package io.quarkus.resteasy.reactive.server.test; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; + +public class BothBlockingAndNonBlockingOnApplicationTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class, MyApplication.class); + } + }).setExpectedException(DeploymentException.class); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + public static class Resource { + + @Path("hello") + public String hello() { + return "hello"; + } + } + + @ApplicationPath("/app") + @Blocking + @NonBlocking + public static class MyApplication extends Application { + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnClassTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnClassTest.java new file mode 100644 index 0000000000000..dafd14a08adae --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnClassTest.java @@ -0,0 +1,46 @@ +package io.quarkus.resteasy.reactive.server.test; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; + +public class BothBlockingAndNonBlockingOnClassTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class); + } + }).setExpectedException(DeploymentException.class); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + @Blocking + @NonBlocking + public static class Resource { + + @Path("hello") + public String hello() { + return "hello"; + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnMethodTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnMethodTest.java new file mode 100644 index 0000000000000..0cb2def5be083 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/BothBlockingAndNonBlockingOnMethodTest.java @@ -0,0 +1,46 @@ +package io.quarkus.resteasy.reactive.server.test; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; + +public class BothBlockingAndNonBlockingOnMethodTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class); + } + }).setExpectedException(DeploymentException.class); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + public static class Resource { + + @Path("hello") + @Blocking + @NonBlocking + public String hello() { + return "hello"; + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/InvalidEncodingTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/InvalidEncodingTest.java new file mode 100644 index 0000000000000..8f7f829a0f0fe --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/InvalidEncodingTest.java @@ -0,0 +1,74 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.not; + +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.reactive.MultipartForm; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.builder.MultiPartSpecBuilder; +import io.restassured.specification.MultiPartSpecification; + +public class InvalidEncodingTest { + + private static final String TEXT_WITH_ACCENTED_CHARACTERS = "Text with UTF-8 accented characters: é à è"; + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(FeedbackBody.class, FeedbackResource.class) + .addAsResource(new StringAsset( + "quarkus.resteasy-reactive.multipart.input-part.default-charset=us-ascii"), + "application.properties")); + + @Test + public void testMultipartEncoding() throws URISyntaxException { + MultiPartSpecification multiPartSpecification = new MultiPartSpecBuilder(TEXT_WITH_ACCENTED_CHARACTERS) + .controlName("content") + // we need to force the content-type to avoid having the charset included + // as we are testing the default behavior when no charset is defined + .header("Content-Type", "text/plain") + .charset(StandardCharsets.UTF_8) + .build(); + + RestAssured + .given() + .multiPart(multiPartSpecification) + .post("/test/multipart-encoding") + .then() + .statusCode(200) + .body(not(TEXT_WITH_ACCENTED_CHARACTERS)); + } + + @Path("/test") + public static class FeedbackResource { + + @POST + @Path("/multipart-encoding") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA + ";charset=UTF-8") + public String postForm(@MultipartForm final FeedbackBody feedback) { + return feedback.content; + } + } + + public static class FeedbackBody { + @RestForm("content") + public String content; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java index 7c60b603e53d7..020a3458cb1d3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java @@ -134,8 +134,7 @@ public void testAnnotationFreeSubresource() throws Exception { Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); Assertions.assertEquals(response.readEntity(String.class), "got"); Assertions.assertNotNull(response.getHeaderString("Content-Type")); - Assertions.assertNotNull(response.getHeaderString("Content-Type")); - Assertions.assertEquals(MediaType.TEXT_PLAIN_TYPE.toString(), + Assertions.assertEquals("text/plain;charset=UTF-8", response.getHeaderString("Content-Type")); } { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/DefaultMediaTypeResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/DefaultMediaTypeResource.java index 5f06495bbdcf2..bbac524ac79ae 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/DefaultMediaTypeResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/DefaultMediaTypeResource.java @@ -51,14 +51,14 @@ public Response postFoo(String source) throws Exception { @Produces(MediaType.TEXT_PLAIN) @POST public Response postIntProduce(String source) throws Exception { - return Response.ok().entity(new Integer(8)).build(); + return Response.ok().entity(8).build(); } @Path("postInt") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @POST public Response postInt(String source) throws Exception { - return Response.ok().entity(new Integer(8)).build(); + return Response.ok().entity(8).build(); } @Path("postIntegerProduce") diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthRolesAllowedJaxRsTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthRolesAllowedJaxRsTestCase.java index 8c6479555944b..05d04683d6858 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthRolesAllowedJaxRsTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthRolesAllowedJaxRsTestCase.java @@ -2,6 +2,8 @@ import static org.hamcrest.Matchers.is; +import java.util.Arrays; + import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -18,7 +20,7 @@ public class LazyAuthRolesAllowedJaxRsTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(RolesAllowedResource.class, UserResource.class, + .addClasses(RolesAllowedResource.class, RolesAllowedBlockingResource.class, UserResource.class, TestIdentityProvider.class, TestIdentityController.class, UnsecuredSubResource.class) @@ -34,12 +36,14 @@ public static void setupUsers() { @Test public void testRolesAllowed() { - RestAssured.get("/roles").then().statusCode(401); - RestAssured.given().auth().basic("admin", "admin").get("/roles").then().statusCode(200); - RestAssured.given().auth().basic("admin", "wrong").get("/roles").then().statusCode(401); - RestAssured.given().auth().basic("user", "user").get("/roles").then().statusCode(200); - RestAssured.given().auth().basic("admin", "admin").get("/roles/admin").then().statusCode(200); - RestAssured.given().auth().basic("user", "user").get("/roles/admin").then().statusCode(403); + Arrays.asList("/roles", "/roles-blocking").forEach((path) -> { + RestAssured.get(path).then().statusCode(401); + RestAssured.given().auth().basic("admin", "admin").get(path).then().statusCode(200); + RestAssured.given().auth().basic("admin", "wrong").get(path).then().statusCode(401); + RestAssured.given().auth().basic("user", "user").get(path).then().statusCode(200); + RestAssured.given().auth().basic("admin", "admin").get(path + "/admin").then().statusCode(200); + RestAssured.given().auth().basic("user", "user").get(path + "/admin").then().statusCode(403); + }); } @Test diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ReplaceIdentityLazyAuthRolesAllowedJaxRsTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ReplaceIdentityLazyAuthRolesAllowedJaxRsTestCase.java index 38492b3ef2346..8654e008e8bd8 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ReplaceIdentityLazyAuthRolesAllowedJaxRsTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ReplaceIdentityLazyAuthRolesAllowedJaxRsTestCase.java @@ -2,6 +2,8 @@ import static org.hamcrest.Matchers.is; +import java.util.Arrays; + import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -18,7 +20,7 @@ public class ReplaceIdentityLazyAuthRolesAllowedJaxRsTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(RolesAllowedResource.class, UserResource.class, + .addClasses(RolesAllowedResource.class, UserResource.class, RolesAllowedBlockingResource.class, TestIdentityProvider.class, TestIdentityController.class, SecurityOverrideFilter.class, @@ -36,14 +38,17 @@ public static void setupUsers() { @Test public void testRolesAllowedModified() { //make sure that things work as normal when no modification happens - RestAssured.given() - .header("user", "admin") - .header("role", "admin") - .get("/roles").then().statusCode(200); - RestAssured.given() - .auth().basic("user", "user") - .header("user", "admin") - .header("role", "admin").get("/roles/admin").then().statusCode(200); + + Arrays.asList("/roles", "/roles-blocking").forEach((path) -> { + RestAssured.given() + .header("user", "admin") + .header("role", "admin") + .get(path).then().statusCode(200); + RestAssured.given() + .auth().basic("user", "user") + .header("user", "admin") + .header("role", "admin").get(path + "/admin").then().statusCode(200); + }); } @Test diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedBlockingResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedBlockingResource.java new file mode 100644 index 0000000000000..6d44a2d7e0faf --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedBlockingResource.java @@ -0,0 +1,30 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import io.smallrye.common.annotation.Blocking; + +/** + * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com + */ +@Path("/roles-blocking") +@PermitAll +@Blocking +public class RolesAllowedBlockingResource { + @GET + @RolesAllowed({ "user", "admin" }) + public String defaultSecurity() { + return "default"; + } + + @Path("/admin") + @RolesAllowed("admin") + @GET + public String admin() { + return "admin"; + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedJaxRsTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedJaxRsTestCase.java index e55d06541c6c6..9f46e69e9d9e1 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedJaxRsTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedJaxRsTestCase.java @@ -2,8 +2,21 @@ import static org.hamcrest.Matchers.is; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Arrays; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; + import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -17,7 +30,8 @@ public class RolesAllowedJaxRsTestCase { @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(RolesAllowedResource.class, UserResource.class, + .addClasses(RolesAllowedResource.class, UserResource.class, RolesAllowedBlockingResource.class, + SerializationEntity.class, SerializationRolesResource.class, TestIdentityProvider.class, TestIdentityController.class, UnsecuredSubResource.class)); @@ -31,11 +45,13 @@ public static void setupUsers() { @Test public void testRolesAllowed() { - RestAssured.get("/roles").then().statusCode(401); - RestAssured.given().auth().basic("admin", "admin").get("/roles").then().statusCode(200); - RestAssured.given().auth().basic("user", "user").get("/roles").then().statusCode(200); - RestAssured.given().auth().basic("admin", "admin").get("/roles/admin").then().statusCode(200); - RestAssured.given().auth().basic("user", "user").get("/roles/admin").then().statusCode(403); + Arrays.asList("/roles", "/roles-blocking").forEach((path) -> { + RestAssured.get(path).then().statusCode(401); + RestAssured.given().auth().basic("admin", "admin").get(path).then().statusCode(200); + RestAssured.given().auth().basic("user", "user").get(path).then().statusCode(200); + RestAssured.given().auth().basic("admin", "admin").get(path + "/admin").then().statusCode(200); + RestAssured.given().auth().basic("user", "user").get(path + "/admin").then().statusCode(403); + }); } @Test @@ -46,4 +62,47 @@ public void testUser() { RestAssured.given().auth().basic("user", "user").get("/user").then().body(is("")); RestAssured.given().auth().preemptive().basic("user", "user").get("/user").then().body(is("user")); } + + @Test + public void testSecurityRunsBeforeValidation() { + read = false; + RestAssured.given().body(new SerializationEntity()).post("/roles-validate").then().statusCode(401); + Assertions.assertFalse(read); + RestAssured.given().body(new SerializationEntity()).auth().basic("admin", "admin").post("/roles-validate").then() + .statusCode(200); + Assertions.assertTrue(read); + read = false; + RestAssured.given().body(new SerializationEntity()).auth().basic("user", "user").post("/roles-validate").then() + .statusCode(200); + Assertions.assertTrue(read); + read = false; + RestAssured.given().body(new SerializationEntity()).auth().basic("admin", "admin").post("/roles-validate/admin").then() + .statusCode(200); + Assertions.assertTrue(read); + read = false; + RestAssured.given().body(new SerializationEntity()).auth().basic("user", "user").post("/roles-validate/admin").then() + .statusCode(403); + Assertions.assertFalse(read); + } + + static volatile boolean read = false; + + @Provider + public static class Reader implements MessageBodyReader { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return true; + } + + @Override + public SerializationEntity readFrom(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + read = true; + SerializationEntity entity = new SerializationEntity(); + entity.setName("read"); + return entity; + } + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedResource.java index 51279ee6b74a1..842e04d7d0ec8 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/RolesAllowedResource.java @@ -5,14 +5,11 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; -import io.smallrye.common.annotation.Blocking; - /** * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com */ @Path("/roles") @PermitAll -@Blocking public class RolesAllowedResource { @GET @RolesAllowed({ "user", "admin" }) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationEntity.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationEntity.java new file mode 100644 index 0000000000000..bd1a9774f5262 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationEntity.java @@ -0,0 +1,14 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +public class SerializationEntity { + private String name; + + public String getName() { + return name; + } + + public SerializationEntity setName(String name) { + this.name = name; + return this; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationRolesResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationRolesResource.java new file mode 100644 index 0000000000000..e2b273183fa33 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/SerializationRolesResource.java @@ -0,0 +1,27 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import io.smallrye.common.annotation.Blocking; + +@Path("/roles-validate") +@PermitAll +@Blocking +public class SerializationRolesResource { + @POST + @RolesAllowed({ "user", "admin" }) + public String defaultSecurity(SerializationEntity entity) { + return entity.getName(); + } + + @Path("/admin") + @RolesAllowed("admin") + @POST + public String admin(SerializationEntity entity) { + return entity.getName(); + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/LocalDateCustomParamConverterProviderTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/LocalDateCustomParamConverterProviderTest.java index 2f7da02dc2298..4addbe7ad3790 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/LocalDateCustomParamConverterProviderTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/LocalDateCustomParamConverterProviderTest.java @@ -73,6 +73,7 @@ public String toString(LocalDate value) { @Provider public static class CustomLocalDateParamConverterProvider implements ParamConverterProvider { + @SuppressWarnings("unchecked") @Override public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { if (rawType == LocalDate.class) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/MyParameterProvider.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/MyParameterProvider.java index 40a21c5558ff4..7937e776b45d4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/MyParameterProvider.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/MyParameterProvider.java @@ -9,6 +9,8 @@ @Provider public class MyParameterProvider implements ParamConverterProvider { + + @SuppressWarnings("unchecked") @Override public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { if (rawType == MyParameter.class) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stress/SuspendResumeStressTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stress/SuspendResumeStressTest.java new file mode 100644 index 0000000000000..8ee15d769d0c0 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stress/SuspendResumeStressTest.java @@ -0,0 +1,106 @@ +package io.quarkus.resteasy.reactive.server.test.stress; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.hamcrest.Matchers; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.resteasy.reactive.common.model.ResourceClass; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; +import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; +import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +/** + * Tests lots of suspends/resumes per request + */ +public class SuspendResumeStressTest { + + private static volatile ExecutorService executorService; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .addBuildChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new MethodScannerBuildItem(new MethodScanner() { + @Override + public List scan(MethodInfo method, ClassInfo actualEndpointClass, + Map methodContext) { + return Collections.singletonList(new Custom()); + } + })); + } + }).produces(MethodScannerBuildItem.class).build(); + } + }) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(HelloResource.class)); + + @Test + public void testSuspendResumeStressTest() { + executorService = Executors.newFixedThreadPool(10); + try { + for (int i = 0; i < 100; ++i) { + RestAssured.when().get("/hello").then().body(Matchers.is("hello")); + } + } finally { + executorService.shutdownNow(); + executorService = null; + } + } + + @Path("hello") + public static class HelloResource { + + @GET + public String hello() { + return "hello"; + } + + } + + public static class Custom implements HandlerChainCustomizer { + @Override + public List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) { + List handlers = new ArrayList<>(); + for (int i = 0; i < 100; ++i) { + handlers.add(new ResumeHandler()); + } + return handlers; + } + } + + public static class ResumeHandler implements ServerRestHandler { + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + requestContext.suspend(); + executorService.execute(requestContext::resume); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/pom.xml index 8c953594ebc94..a580781b3e548 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/pom.xml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/pom.xml @@ -63,6 +63,18 @@ + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + +
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusResteasyReactiveRequestContext.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusResteasyReactiveRequestContext.java index 33ae9c3b599cf..822cc2e5d30e3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusResteasyReactiveRequestContext.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusResteasyReactiveRequestContext.java @@ -18,6 +18,7 @@ public class QuarkusResteasyReactiveRequestContext extends VertxResteasyReactiveRequestContext { final CurrentIdentityAssociation association; + boolean userSetup = false; public QuarkusResteasyReactiveRequestContext(Deployment deployment, ProvidersImpl providers, RoutingContext context, ThreadSetupAction requestContext, ServerRestHandler[] handlerChain, @@ -29,7 +30,8 @@ public QuarkusResteasyReactiveRequestContext(Deployment deployment, ProvidersImp protected void handleRequestScopeActivation() { super.handleRequestScopeActivation(); - if (association != null) { + if (!userSetup && association != null) { + userSetup = true; QuarkusHttpUser existing = (QuarkusHttpUser) context.user(); if (existing != null) { SecurityIdentity identity = existing.getSecurityIdentity(); @@ -64,6 +66,7 @@ public void handleUnmappedException(Throwable throwable) { throw sneakyThrow(throwable); } + @SuppressWarnings("unchecked") private RuntimeException sneakyThrow(Throwable e) throws E { throw (E) e; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRuntimeRecorder.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRuntimeRecorder.java index 597dee682f820..54633188e5330 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRuntimeRecorder.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRuntimeRecorder.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.runtime; +import java.nio.charset.Charset; import java.time.Duration; import java.util.List; import java.util.Optional; @@ -15,14 +16,15 @@ @Recorder public class ResteasyReactiveRuntimeRecorder { - public void configure(RuntimeValue deployment, HttpConfiguration configuration) { + public void configure(RuntimeValue deployment, HttpConfiguration httpConf, + ResteasyReactiveServerRuntimeConfig runtimeConf) { List runtimeConfigurableServerRestHandlers = deployment.getValue() .getRuntimeConfigurableServerRestHandlers(); for (RuntimeConfigurableServerRestHandler handler : runtimeConfigurableServerRestHandlers) { handler.configure(new RuntimeConfiguration() { @Override public Duration readTimeout() { - return configuration.readTimeout; + return httpConf.readTimeout; } @Override @@ -30,12 +32,17 @@ public Body body() { return new Body() { @Override public boolean deleteUploadedFilesOnEnd() { - return configuration.body.deleteUploadedFilesOnEnd; + return httpConf.body.deleteUploadedFilesOnEnd; } @Override public String uploadsDirectory() { - return configuration.body.uploadsDirectory; + return httpConf.body.uploadsDirectory; + } + + @Override + public Charset defaultCharset() { + return runtimeConf.multipart.inputPart.defaultCharset; } }; } @@ -45,8 +52,8 @@ public Limits limits() { return new Limits() { @Override public Optional maxBodySize() { - if (configuration.limits.maxBodySize.isPresent()) { - return Optional.of(configuration.limits.maxBodySize.get().asLongValue()); + if (httpConf.limits.maxBodySize.isPresent()) { + return Optional.of(httpConf.limits.maxBodySize.get().asLongValue()); } else { return Optional.empty(); } @@ -54,7 +61,7 @@ public Optional maxBodySize() { @Override public long maxFormAttributeSize() { - return configuration.limits.maxFormAttributeSize.asLongValue(); + return httpConf.limits.maxFormAttributeSize.asLongValue(); } }; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveServerRuntimeConfig.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveServerRuntimeConfig.java new file mode 100644 index 0000000000000..cff31220d4ad8 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveServerRuntimeConfig.java @@ -0,0 +1,38 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import java.nio.charset.Charset; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "resteasy-reactive", phase = ConfigPhase.RUN_TIME) +public class ResteasyReactiveServerRuntimeConfig { + + /** + * Input part configuration. + */ + @ConfigItem + public MultipartConfigGroup multipart; + + @ConfigGroup + public static class MultipartConfigGroup { + + /** + * Input part configuration. + */ + @ConfigItem + public InputPartConfigGroup inputPart; + } + + @ConfigGroup + public static class InputPartConfigGroup { + + /** + * Default charset. + */ + @ConfigItem(defaultValue = "UTF-8") + public Charset defaultCharset; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java new file mode 100644 index 0000000000000..47ce0b0afa449 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java @@ -0,0 +1,97 @@ +package io.quarkus.resteasy.reactive.server.runtime.security; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import org.jboss.resteasy.reactive.common.model.ResourceClass; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; +import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.security.identity.CurrentIdentityAssociation; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; +import io.smallrye.mutiny.subscription.UniSubscriber; +import io.smallrye.mutiny.subscription.UniSubscription; + +public class EagerSecurityHandler implements ServerRestHandler { + + private static final SecurityCheck NULL_SENTINEL = new SecurityCheck() { + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + + } + }; + + private volatile InjectableInstance currentIdentityAssociation; + private volatile SecurityCheck check; + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + SecurityCheck check = this.check; + if (check == null) { + check = Arc.container().instance(SecurityCheckStorage.class).get() + .getSecurityCheck(requestContext.getTarget().getLazyMethod().getMethod()); + if (check == null) { + check = NULL_SENTINEL; + } + this.check = check; + } + if (check == NULL_SENTINEL) { + return; + } + requestContext.requireCDIRequestScope(); + requestContext.suspend(); + SecurityCheck theCheck = check; + getCurrentIdentityAssociation().get().getDeferredIdentity().map(new Function() { + @Override + public Object apply(SecurityIdentity securityIdentity) { + theCheck.apply(securityIdentity, requestContext.getTarget().getLazyMethod().getMethod(), + requestContext.getParameters()); + return null; + } + }) + .subscribe().withSubscriber(new UniSubscriber() { + @Override + public void onSubscribe(UniSubscription subscription) { + + } + + @Override + public void onItem(Object item) { + requestContext.resume(); + } + + @Override + public void onFailure(Throwable failure) { + requestContext.resume(failure); + } + }); + + } + + private InjectableInstance getCurrentIdentityAssociation() { + InjectableInstance identityAssociation = this.currentIdentityAssociation; + if (identityAssociation == null) { + return this.currentIdentityAssociation = Arc.container().select(CurrentIdentityAssociation.class); + } + return identityAssociation; + } + + public static class Customizer implements HandlerChainCustomizer { + @Override + public List handlers(Phase phase, ResourceClass resourceClass, + ServerResourceMethod serverResourceMethod) { + if (phase == Phase.AFTER_MATCH) { + return Collections.singletonList(new EagerSecurityHandler()); + } + return Collections.emptyList(); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/SecurityContextOverrideHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/SecurityContextOverrideHandler.java index 640b9ae838a67..9f346c1d56ea3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/SecurityContextOverrideHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/SecurityContextOverrideHandler.java @@ -75,6 +75,7 @@ public boolean hasRole(String role) { return modified.isUserInRole(role); } + @SuppressWarnings("unchecked") @Override public T getCredential(Class credentialType) { for (Credential cred : getCredentials()) { @@ -90,6 +91,7 @@ public Set getCredentials() { return oldCredentials; } + @SuppressWarnings("unchecked") @Override public T getAttribute(String name) { return (T) oldAttributes.get(name); @@ -122,7 +124,10 @@ public static class Customizer implements HandlerChainCustomizer { @Override public List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod serverResourceMethod) { - return Collections.singletonList(new SecurityContextOverrideHandler()); + if (phase == Phase.AFTER_PRE_MATCH) { + return Collections.singletonList(new SecurityContextOverrideHandler()); + } + return Collections.emptyList(); } } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/websocket/VertxWebSocketRestHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/websocket/VertxWebSocketRestHandler.java index 8cba0014a122f..cd7762bcb7b1d 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/websocket/VertxWebSocketRestHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/websocket/VertxWebSocketRestHandler.java @@ -3,8 +3,10 @@ import java.util.Collections; import java.util.List; +import org.jboss.resteasy.reactive.common.model.ResourceClass; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; +import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; public class VertxWebSocketRestHandler implements HandlerChainCustomizer { @@ -21,7 +23,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) }; @Override - public List handlers(Phase phase) { + public List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) { if (phase == Phase.AFTER_METHOD_INVOKE) { return Collections.singletonList(new ServerRestHandler() { @Override diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml index fbfbcb590176c..402a4befff480 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,7 +9,7 @@ metadata: categories: - "web" - "reactive" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 3514610f54165..29869901c8d2b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -13,7 +13,7 @@ metadata: categories: - "web" - "serialization" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml b/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml index d60bc5768a1a9..207e881efd431 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml @@ -26,6 +26,11 @@ quarkus-resteasy-reactive-jackson-deployment test + + io.quarkus + quarkus-smallrye-fault-tolerance-deployment + test + io.quarkus quarkus-junit5-internal diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/ResponseExceptionMapperTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/ResponseExceptionMapperTest.java new file mode 100644 index 0000000000000..eaba83a700fe0 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/ResponseExceptionMapperTest.java @@ -0,0 +1,70 @@ +package io.quarkus.rest.client.reactive.error; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ResponseExceptionMapperTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Client.class, Resource.class, MyExceptionMapper.class).addAsResource( + new StringAsset(setUrlForClass(Client.class)), + "application.properties")); + public static final String ERROR_MESSAGE = "The entity was not found"; + + @RestClient + Client client; + + @Test + void shouldInvokeExceptionMapperOnce() { + assertThrows(RuntimeException.class, client::get); + assertThat(MyExceptionMapper.executionCount.get()).isEqualTo(1); + } + + @Path("/error") + public static class Resource { + @GET + public Response returnError() { + return Response.status(404).entity(ERROR_MESSAGE).build(); + } + } + + @Path("/error") + @RegisterRestClient + public interface Client { + @GET + String get(); + } + + @Provider + public static class MyExceptionMapper implements ResponseExceptionMapper { + + private static final AtomicInteger executionCount = new AtomicInteger(); + + @Override + public Exception toThrowable(Response response) { + executionCount.incrementAndGet(); + return new RuntimeException("My exception"); + } + } + +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/AsyncRestClientFallbackTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/AsyncRestClientFallbackTest.java new file mode 100644 index 0000000000000..2a46b22922e5b --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/AsyncRestClientFallbackTest.java @@ -0,0 +1,68 @@ +package io.quarkus.rest.client.reactive.ft; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class AsyncRestClientFallbackTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestEndpoint.class, Client.class, MyFallback.class) + .addAsResource(new StringAsset(setUrlForClass(Client.class)), "application.properties")); + + @Inject + @RestClient + Client client; + + @Test + public void testFallbackWasUsed() throws ExecutionException, InterruptedException { + assertEquals("pong", client.ping().toCompletableFuture().get()); + } + + @Path("/test") + public static class TestEndpoint { + @GET + public String get() { + throw new WebApplicationException(404); + } + } + + @RegisterRestClient + public interface Client { + @GET + @Path("/test") + @Asynchronous + @Fallback(MyFallback.class) + CompletionStage ping(); + } + + public static class MyFallback implements FallbackHandler> { + @Override + public CompletionStage handle(ExecutionContext context) { + return completedFuture("pong"); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/RestClientFallbackTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/RestClientFallbackTest.java new file mode 100644 index 0000000000000..da3dfd95e1b8f --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/RestClientFallbackTest.java @@ -0,0 +1,63 @@ +package io.quarkus.rest.client.reactive.ft; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class RestClientFallbackTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestEndpoint.class, Client.class, MyFallback.class) + .addAsResource(new StringAsset(setUrlForClass(Client.class)), "application.properties")); + + @Inject + @RestClient + Client client; + + @Test + public void testFallbackWasUsed() { + assertEquals("pong", client.ping()); + } + + @Path("/test") + public static class TestEndpoint { + @GET + public String get() { + throw new WebApplicationException(404); + } + } + + @RegisterRestClient + public interface Client { + @GET + @Path("/test") + @Fallback(MyFallback.class) + String ping(); + } + + public static class MyFallback implements FallbackHandler { + @Override + public String handle(ExecutionContext context) { + return "pong"; + } + + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/UniRestClientFallbackTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/UniRestClientFallbackTest.java new file mode 100644 index 0000000000000..360eefb016c0b --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ft/UniRestClientFallbackTest.java @@ -0,0 +1,63 @@ +package io.quarkus.rest.client.reactive.ft; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +public class UniRestClientFallbackTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestEndpoint.class, Client.class, MyFallback.class) + .addAsResource(new StringAsset(setUrlForClass(Client.class)), "application.properties")); + + @Inject + @RestClient + Client client; + + @Test + public void testFallbackWasUsed() { + assertEquals("pong", client.ping().await().indefinitely()); + } + + @Path("/test") + public static class TestEndpoint { + @GET + public String get() { + throw new WebApplicationException(404); + } + } + + @RegisterRestClient + public interface Client { + @GET + @Path("/test") + @Fallback(MyFallback.class) + Uni ping(); + } + + public static class MyFallback implements FallbackHandler> { + @Override + public Uni handle(ExecutionContext context) { + return Uni.createFrom().item("pong"); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 086d835ad797d..1383334d0c38c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,7 +8,7 @@ metadata: - "resteasy-reactive" categories: - "web" - status: "preview" + status: "stable" codestart: name: "resteasy-reactive" languages: diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java index e375e13b05762..6a026748a7ddb 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java @@ -3,7 +3,7 @@ import org.jboss.jandex.MethodInfo; import io.quarkus.builder.item.MultiBuildItem; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; /** * Used as an integration point when extensions need to customize the security behavior of a bean diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index d6bd449991c80..38bbfd7767438 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -17,6 +17,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import javax.enterprise.context.ApplicationScoped; @@ -59,13 +60,13 @@ import io.quarkus.security.runtime.interceptor.DenyAllInterceptor; import io.quarkus.security.runtime.interceptor.PermitAllInterceptor; import io.quarkus.security.runtime.interceptor.RolesAllowedInterceptor; -import io.quarkus.security.runtime.interceptor.SecurityCheckStorage; import io.quarkus.security.runtime.interceptor.SecurityCheckStorageBuilder; import io.quarkus.security.runtime.interceptor.SecurityConstrainer; import io.quarkus.security.runtime.interceptor.SecurityHandler; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; import io.quarkus.security.spi.AdditionalSecuredClassesBuildItem; import io.quarkus.security.spi.runtime.AuthorizationController; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; public class SecurityProcessor { @@ -321,7 +322,7 @@ void gatherSecurityChecks(BuildProducer syntheticBeans, List additionalSecuredClasses, SecurityCheckRecorder recorder, List additionalSecurityChecks, SecurityBuildTimeConfig config) { - classPredicate.produce(new ApplicationClassPredicateBuildItem(new SecurityCheckStorage.AppPredicate())); + classPredicate.produce(new ApplicationClassPredicateBuildItem(new SecurityCheckStorageAppPredicate())); final Map additionalSecured = new HashMap<>(); for (AdditionalSecuredClassesBuildItem securedClasses : additionalSecuredClasses) { @@ -510,4 +511,12 @@ static class AdditionalSecured { this.rolesAllowed = rolesAllowed; } } + + class SecurityCheckStorageAppPredicate implements Predicate { + + @Override + public boolean test(String s) { + return s.equals(SecurityCheckStorage.class.getName()); + } + } } diff --git a/extensions/security/runtime-spi/pom.xml b/extensions/security/runtime-spi/pom.xml index cffacf35a78d2..d1136b1e0c229 100644 --- a/extensions/security/runtime-spi/pom.xml +++ b/extensions/security/runtime-spi/pom.xml @@ -17,6 +17,10 @@ io.quarkus quarkus-core + + io.quarkus.security + quarkus-security + diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java similarity index 78% rename from extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java rename to extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java index bee1e06417c61..ba99d500f77f0 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java +++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java @@ -1,4 +1,4 @@ -package io.quarkus.security.runtime.interceptor.check; +package io.quarkus.security.spi.runtime; import java.lang.reflect.Method; diff --git a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java new file mode 100644 index 0000000000000..100473577027b --- /dev/null +++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java @@ -0,0 +1,8 @@ +package io.quarkus.security.spi.runtime; + +import java.lang.reflect.Method; + +public interface SecurityCheckStorage { + SecurityCheck getSecurityCheck(Method method); + +} diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java index a467e2bf93e8e..4d3b4e0d23e77 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java @@ -2,13 +2,13 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.security.runtime.interceptor.SecurityCheckStorage; import io.quarkus.security.runtime.interceptor.SecurityCheckStorageBuilder; import io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck; import io.quarkus.security.runtime.interceptor.check.DenyAllCheck; import io.quarkus.security.runtime.interceptor.check.PermitAllCheck; import io.quarkus.security.runtime.interceptor.check.RolesAllowedCheck; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; @Recorder public class SecurityCheckRecorder { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorage.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorage.java deleted file mode 100644 index 5433c30d842fe..0000000000000 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorage.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.quarkus.security.runtime.interceptor; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; - -public interface SecurityCheckStorage { - SecurityCheck getSecurityCheck(Method method); - - class AppPredicate implements Predicate { - - @Override - public boolean test(String s) { - return s.equals(SecurityCheckStorage.class.getName()); - } - } -} diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java index 667614a32d180..dda9083bfae9a 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java @@ -6,7 +6,8 @@ import java.util.Map; import java.util.Objects; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; public class SecurityCheckStorageBuilder { private final Map securityChecks = new HashMap<>(); diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java index 380d804b55500..75ba078df3060 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java @@ -6,7 +6,8 @@ import javax.inject.Singleton; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; /** * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java index 7f05682ac1323..4b23692988914 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java @@ -4,6 +4,7 @@ import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; public class AuthenticatedCheck implements SecurityCheck { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java index 70e1d0d832771..af0c0f5a97fd4 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java @@ -5,6 +5,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; public class DenyAllCheck implements SecurityCheck { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java index c3be76e05f1d4..ef8e023db721c 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java @@ -3,6 +3,7 @@ import java.lang.reflect.Method; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; public class PermitAllCheck implements SecurityCheck { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java index 51f072727411c..3ea82eb109ef0 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java @@ -12,6 +12,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; public class RolesAllowedCheck implements SecurityCheck { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SupplierRolesAllowedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SupplierRolesAllowedCheck.java index 156c9d983ca43..f4b3cb2ce9f53 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SupplierRolesAllowedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SupplierRolesAllowedCheck.java @@ -6,6 +6,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.SecurityCheck; public class SupplierRolesAllowedCheck implements SecurityCheck { diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java index e787009138a8e..849d004c98b55 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java @@ -16,10 +16,11 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; import io.quarkus.arc.processor.AnnotationStore; import io.quarkus.deployment.builditem.AnnotationProxyBuildItem; -import io.quarkus.deployment.util.JandexUtil; +import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ClassOutput; import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; @@ -37,12 +38,15 @@ final class FaultToleranceScanner { private final AnnotationProxyBuildItem proxy; private final ClassOutput output; + private final RecorderContext recorderContext; + FaultToleranceScanner(IndexView index, AnnotationStore annotationStore, AnnotationProxyBuildItem proxy, - ClassOutput output) { + ClassOutput output, RecorderContext recorderContext) { this.index = index; this.annotationStore = annotationStore; this.proxy = proxy; this.output = output; + this.recorderContext = recorderContext; } boolean hasFTAnnotations(ClassInfo clazz) { @@ -100,7 +104,7 @@ FaultToleranceMethod createFaultToleranceMethod(ClassInfo beanClass, MethodInfo FaultToleranceMethod result = new FaultToleranceMethod(); - result.beanClass = load(beanClass.name()); + result.beanClass = getClassProxy(beanClass); result.method = createMethodDescriptor(method); result.asynchronous = getAnnotation(Asynchronous.class, method, beanClass, annotationsPresentDirectly); @@ -125,13 +129,13 @@ FaultToleranceMethod createFaultToleranceMethod(ClassInfo beanClass, MethodInfo private MethodDescriptor createMethodDescriptor(MethodInfo method) { MethodDescriptor result = new MethodDescriptor(); - result.declaringClass = load(method.declaringClass().name()); + result.declaringClass = getClassProxy(method.declaringClass()); result.name = method.name(); result.parameterTypes = method.parameters() .stream() - .map(JandexUtil::loadRawType) + .map(this::getClassProxy) .toArray(Class[]::new); - result.returnType = JandexUtil.loadRawType(method.returnType()); + result.returnType = getClassProxy(method.returnType()); return result; } @@ -171,11 +175,15 @@ private A createAnnotation(Class annotationType, Annot return proxy.builder(instance, annotationType).build(output); } - private static Class load(DotName name) { - try { - return Thread.currentThread().getContextClassLoader().loadClass(name.toString()); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } + // using class proxies instead of attempting to load the class, because the class + // doesn't have to exist in the deployment classloader at all -- instead, it may be + // generated by another Quarkus extension (such as RestClient Reactive) + private Class getClassProxy(ClassInfo clazz) { + return recorderContext.classProxy(clazz.name().toString()); + } + + private Class getClassProxy(Type type) { + // Type.name() returns the right thing + return recorderContext.classProxy(type.name().toString()); } } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index 0f8b969f90ccc..f3430bb8568fc 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -54,6 +54,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ClassOutput; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusAsyncExecutorProvider; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusExistingCircuitBreakerNames; @@ -236,6 +237,7 @@ public void transform(TransformationContext ctx) { // needs to be RUNTIME_INIT because we need to read MP Config @Record(ExecutionTime.RUNTIME_INIT) void validateFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, + RecorderContext recorderContext, ValidationPhaseBuildItem validationPhase, BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, AnnotationProxyBuildItem annotationProxy, @@ -248,7 +250,8 @@ void validateFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, // none of them are application classes ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, false); - FaultToleranceScanner scaner = new FaultToleranceScanner(index, annotationStore, annotationProxy, classOutput); + FaultToleranceScanner scaner = new FaultToleranceScanner(index, annotationStore, annotationProxy, classOutput, + recorderContext); List ftMethods = new ArrayList<>(); List exceptions = new ArrayList<>(); diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/QuarkusMediatorConfigurationUtil.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/QuarkusMediatorConfigurationUtil.java index 2782758be2274..a4452ae8f6631 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/QuarkusMediatorConfigurationUtil.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/QuarkusMediatorConfigurationUtil.java @@ -5,12 +5,15 @@ import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.BROADCAST; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.COMPLETION_STAGE; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.INCOMING; +import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.INCOMINGS; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.KOTLIN_UNIT; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.MERGE; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.OUTGOING; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.SMALLRYE_BLOCKING; import static io.quarkus.smallrye.reactivemessaging.deployment.ReactiveMessagingDotNames.VOID_CLASS; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -84,8 +87,11 @@ public static QuarkusMediatorConfiguration create(MethodInfo methodInfo, boolean } configuration.setParameterTypes(parameterTypes); - List incomingValues = getValues(methodInfo, INCOMING); + // We need to extract the value of @Incoming and @Incomings (which contains an array of @Incoming) + List incomingValues = new ArrayList<>(getValues(methodInfo, INCOMING)); + incomingValues.addAll(getIncomingValues(methodInfo)); configuration.setIncomings(incomingValues); + String outgoingValue = getValue(methodInfo, OUTGOING); configuration.setOutgoing(outgoingValue); @@ -247,6 +253,13 @@ private static List getValues(MethodInfo methodInfo, DotName dotName) { .collect(Collectors.toList()); } + private static List getIncomingValues(MethodInfo methodInfo) { + return methodInfo.annotations().stream().filter(ai -> ai.name().equals(INCOMINGS)) + .flatMap(incomings -> Arrays.stream(incomings.value().asNestedArray())) + .map(incoming -> incoming.value().asString()) + .collect(Collectors.toList()); + } + private static String fullMethodName(MethodInfo methodInfo) { return methodInfo.declaringClass() + "#" + methodInfo.name(); } diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/IncomingsTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/IncomingsTest.java new file mode 100644 index 0000000000000..65d4612519656 --- /dev/null +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/IncomingsTest.java @@ -0,0 +1,76 @@ +package io.quarkus.smallrye.reactivemessaging.signatures; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +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 org.reactivestreams.Publisher; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Multi; + +public class IncomingsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ProducerOnA.class, ProducerOnB.class, MyBeanUsingMultipleIncomings.class)); + + @Inject + MyBeanUsingMultipleIncomings bean; + + @Test + public void testIncomingsWithTwoSources() { + await().until(() -> bean.list().size() == 6); + assertThat(bean.list()).containsSubsequence("a", "b", "c"); + assertThat(bean.list()).containsSubsequence("d", "e", "f"); + } + + @ApplicationScoped + public static class ProducerOnA { + + @Outgoing("a") + public Publisher produce() { + return Multi.createFrom().items("a", "b", "c"); + } + + } + + @ApplicationScoped + public static class ProducerOnB { + + @Outgoing("b") + public Publisher produce() { + return Multi.createFrom().items("d", "e", "f"); + } + + } + + @ApplicationScoped + public static class MyBeanUsingMultipleIncomings { + + private final List list = new CopyOnWriteArrayList<>(); + + @Incoming("a") + @Incoming("b") + public void consume(String s) { + list.add(s); + } + + public List list() { + return list; + } + + } +} diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/QuarkusMediatorConfiguration.java b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/QuarkusMediatorConfiguration.java index a714b86e1b9e1..962e56ccce86e 100644 --- a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/QuarkusMediatorConfiguration.java +++ b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/QuarkusMediatorConfiguration.java @@ -212,7 +212,7 @@ public void setInvokerClass(Class invokerClass) { @Override public String methodAsString() { - if (Arc.container() != null) { + if (Arc.container() != null && getBean() != null) { return getBean().getBeanClass().getName() + "#" + getMethodName(); } else { return getMethodName(); diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java index d6c7a8cd702e9..5b0e2850b53c9 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java @@ -43,7 +43,7 @@ import io.quarkus.security.deployment.AdditionalSecurityCheckBuildItem; import io.quarkus.security.deployment.SecurityTransformerUtils; import io.quarkus.security.runtime.SecurityCheckRecorder; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; import io.quarkus.spring.di.deployment.SpringBeanNameToDotNameBuildItem; import io.quarkus.spring.security.runtime.interceptor.SpringPreauthorizeInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecuredInterceptor; diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java index 21088706d7f61..8101bdd0991b6 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java @@ -5,8 +5,8 @@ import java.util.function.Supplier; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; import io.quarkus.security.runtime.interceptor.check.SupplierRolesAllowedCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; import io.quarkus.spring.security.runtime.interceptor.check.AllDelegatingSecurityCheck; import io.quarkus.spring.security.runtime.interceptor.check.AnonymousCheck; import io.quarkus.spring.security.runtime.interceptor.check.AnyDelegatingSecurityCheck; diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java index 0cab79d07c943..429d30a4d0aed 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java @@ -5,7 +5,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; /** * Implementations of this class are generated for expressions in @PreAuthorize that diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java index f3143449ca46e..0e728121457bd 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java @@ -4,7 +4,7 @@ import java.util.List; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; /** * A {@link SecurityCheck} where all delegates must pass diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java index b6dced359c394..b538176065e9b 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java @@ -4,7 +4,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; public class AnonymousCheck implements SecurityCheck { diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java index 7019905db9f31..fdb35a5299f4c 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java @@ -4,7 +4,7 @@ import java.util.List; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; /** * A {@link SecurityCheck} where if any of the delegates passes the security check then diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java index 21ddf73a279a5..8ebee37977075 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java @@ -6,7 +6,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; import io.quarkus.spring.security.runtime.interceptor.accessor.StringPropertyAccessor; /** diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java index e144cdf3759cc..3f2f1f7116daf 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java @@ -5,7 +5,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheck; /** * Instances of this classes are created in order to check if a method parameter diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/PreResponseFilterHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/PreResponseFilterHandler.java new file mode 100644 index 0000000000000..d1fbe2036198a --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/PreResponseFilterHandler.java @@ -0,0 +1,15 @@ +package org.jboss.resteasy.reactive.client.handlers; + +import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; + +/** + * This Handler is invoked before ClientResponseFilters handler. It changes the abort handler chain + * to a one without the ClientResponseFilterRestHandler's so that the filters are not retriggered in case of failure + */ +public class PreResponseFilterHandler implements ClientRestHandler { + @Override + public void handle(RestClientRequestContext requestContext) throws Exception { + requestContext.setAbortHandlerChain(requestContext.getAbortHandlerChainWithoutResponseFilters()); + } +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java index e555bbb0afa88..2b84187250995 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java @@ -256,7 +256,8 @@ RestClientRequestContext performRequestInternal(String httpMethodName, Entity RestClientRequestContext restClientRequestContext = new RestClientRequestContext(restClient, httpClient, httpMethodName, uri, requestSpec.configuration, requestSpec.headers, entity, responseType, registerBodyHandler, properties, handlerChain.createHandlerChain(configuration), - handlerChain.createAbortHandlerChain(configuration), requestContext); + handlerChain.createAbortHandlerChain(configuration), + handlerChain.createAbortHandlerChainWithoutResponseFilters(), requestContext); restClientRequestContext.run(); return restClientRequestContext; } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java index 1859a440fa634..6fbef566db410 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java @@ -10,6 +10,7 @@ import org.jboss.resteasy.reactive.client.handlers.ClientResponseFilterRestHandler; import org.jboss.resteasy.reactive.client.handlers.ClientSendRequestHandler; import org.jboss.resteasy.reactive.client.handlers.ClientSetResponseEntityRestHandler; +import org.jboss.resteasy.reactive.client.handlers.PreResponseFilterHandler; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; @@ -53,6 +54,7 @@ ClientRestHandler[] createHandlerChain(ConfigurationImpl configuration) { } result.add(clientSendHandler); result.add(clientSetResponseEntityRestHandler); + result.add(new PreResponseFilterHandler()); for (int i = 0; i < responseFilters.size(); i++) { result.add(new ClientResponseFilterRestHandler(responseFilters.get(i))); } @@ -63,7 +65,7 @@ ClientRestHandler[] createHandlerChain(ConfigurationImpl configuration) { ClientRestHandler[] createAbortHandlerChain(ConfigurationImpl configuration) { List responseFilters = configuration.getResponseFilters(); if (responseFilters.isEmpty()) { - return new ClientRestHandler[] { clientErrorHandler }; + return createAbortHandlerChainWithoutResponseFilters(); } List result = new ArrayList<>(1 + responseFilters.size()); for (int i = 0; i < responseFilters.size(); i++) { @@ -72,4 +74,8 @@ ClientRestHandler[] createAbortHandlerChain(ConfigurationImpl configuration) { result.add(clientErrorHandler); return result.toArray(EMPTY_REST_HANDLERS); } + + ClientRestHandler[] createAbortHandlerChainWithoutResponseFilters() { + return new ClientRestHandler[] { clientErrorHandler }; + } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java index 19251efed746a..b188ec5ee5911 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java @@ -57,6 +57,7 @@ public class RestClientRequestContext extends AbstractResteasyReactiveContext result; + private final ClientRestHandler[] abortHandlerChainWithoutResponseFilters; /** * Only initialised if we have request or response filters */ @@ -82,6 +83,7 @@ public RestClientRequestContext(ClientImpl restClient, Entity entity, GenericType responseType, boolean registerBodyHandler, Map properties, ClientRestHandler[] handlerChain, ClientRestHandler[] abortHandlerChain, + ClientRestHandler[] abortHandlerChainWithoutResponseFilters, ThreadSetupAction requestContext) { super(handlerChain, abortHandlerChain, requestContext); this.restClient = restClient; @@ -91,6 +93,7 @@ public RestClientRequestContext(ClientImpl restClient, this.requestHeaders = requestHeaders; this.configuration = configuration; this.entity = entity; + this.abortHandlerChainWithoutResponseFilters = abortHandlerChainWithoutResponseFilters; if (responseType == null) { this.responseType = new GenericType<>(String.class); this.checkSuccessfulFamily = false; @@ -108,6 +111,7 @@ public RestClientRequestContext(ClientImpl restClient, } public void abort() { + setAbortHandlerChainStarted(true); restart(abortHandlerChain); } @@ -378,4 +382,8 @@ public boolean isMultipart() { public Map getClientFilterProperties() { return properties; } + + public ClientRestHandler[] getAbortHandlerChainWithoutResponseFilters() { + return abortHandlerChainWithoutResponseFilters; + } } 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 e1a8f4d6eb5bd..517018911779c 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 @@ -54,6 +54,7 @@ import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -469,7 +470,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf boolean validConsumes = false; if (consumes != null) { for (String c : consumes) { - if (c.equals(MediaType.MULTIPART_FORM_DATA)) { + if (c.startsWith(MediaType.MULTIPART_FORM_DATA)) { validConsumes = true; break; } @@ -490,6 +491,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf String[] produces = extractProducesConsumesValues(currentMethodInfo.annotation(PRODUCES), classProduces); produces = applyDefaultProduces(produces, nonAsyncReturnType); + produces = addDefaultCharsets(produces); String sseElementType = classSseElementType; AnnotationInstance sseElementTypeAnnotation = currentMethodInfo.annotation(REST_SSE_ELEMENT_TYPE); @@ -561,6 +563,15 @@ private boolean isBlocking(MethodInfo info, BlockingDefault defaultValue) { Map.Entry nonBlockingAnnotation = getInheritableAnnotation(info, NON_BLOCKING); if ((blockingAnnotation != null) && (nonBlockingAnnotation != null)) { + if (blockingAnnotation.getKey().kind() == nonBlockingAnnotation.getKey().kind()) { + if (blockingAnnotation.getKey().kind() == AnnotationTarget.Kind.METHOD) { + throw new DeploymentException("Method '" + info.name() + "' of class '" + info.declaringClass().name() + + "' contains both @Blocking and @NonBlocking annotations."); + } else { + throw new DeploymentException("Class '" + info.declaringClass().name() + + "' contains both @Blocking and @NonBlocking annotations."); + } + } if (blockingAnnotation.getKey().kind() == AnnotationTarget.Kind.METHOD) { // the most specific annotation was the @Blocking annotation on the method return true; @@ -626,6 +637,22 @@ private String[] applyDefaultProduces(String[] produces, Type nonAsyncReturnType return applyAdditionalDefaults(nonAsyncReturnType); } + // see https://github.com/quarkusio/quarkus/issues/19535 + private String[] addDefaultCharsets(String[] produces) { + if ((produces == null) || (produces.length == 0)) { + return produces; + } + List result = new ArrayList<>(produces.length); + for (String p : produces) { + if (p.equals(MediaType.TEXT_PLAIN)) { + result.add(MediaType.TEXT_PLAIN + ";charset=" + StandardCharsets.UTF_8.name()); + } else { + result.add(p); + } + } + return result.toArray(EMPTY_STRING_ARRAY); + } + protected String[] applyAdditionalDefaults(Type nonAsyncReturnType) { // FIXME: primitives if (STRING.equals(nonAsyncReturnType.name())) @@ -724,7 +751,7 @@ private static String[] extractProducesConsumesValues(AnnotationInstance annotat result.add(t.trim()); } } - return result.toArray(new String[0]); + return result.toArray(EMPTY_STRING_ARRAY); } else { return originalStrings; } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java index a2a9020bc68bb..5464cc8dfbf07 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import javax.enterprise.inject.spi.DeploymentException; import javax.ws.rs.core.Application; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -95,6 +96,10 @@ public static ApplicationScanningResult scanForApplicationClass(IndexView index, throw new RuntimeException("Unable to handle class: " + applicationClass, e); } if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.BLOCKING) != null) { + if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { + throw new DeploymentException("JAX-RS Application class '" + applicationClassInfo.name() + + "' contains both @Blocking and @NonBlocking annotations."); + } blocking = BlockingDefault.BLOCKING; } else if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { blocking = BlockingDefault.NON_BLOCKING; diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java index 8199390a09219..4ec49cbbda50e 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/core/AbstractResteasyReactiveContext.java @@ -32,6 +32,7 @@ public abstract class AbstractResteasyReactiveContext completionCallbacks; private List connectionCallbacks; + private boolean abortHandlerChainStarted; private boolean closed = false; @@ -132,7 +133,6 @@ public void run() { //unless there are pre-mapping filters as these may require CDI boolean disasociateRequestScope = false; boolean aborted = false; - Executor exec = null; try { while (position < handlers.length) { int pos = position; @@ -152,22 +152,14 @@ public void run() { requestScopeActivated = false; requestScopeDeactivated(); } - if (this.executor != null) { - //resume happened in the meantime - suspended = false; - exec = this.executor; - // prevent future suspensions from re-submitting the task - this.executor = null; - return; - } else if (suspended) { - running = false; + if (suspended) { processingSuspended = true; return; } } } } catch (Throwable t) { - aborted = handlers == abortHandlerChain; + aborted = abortHandlerChainStarted; if (t instanceof PreserveTargetException) { handleException(t.getCause(), true); } else { @@ -184,7 +176,6 @@ public void run() { // we need to make sure we don't close the underlying stream in the event loop if the task // has been offloaded to the executor if ((position == handlers.length && !processingSuspended) || aborted) { - exec = null; close(); } else { if (disasociateRequestScope) { @@ -192,14 +183,27 @@ public void run() { currentRequestScope.deactivate(); } beginAsyncProcessing(); - } - if (exec != null) { - //outside sync block - exec.execute(this); - } else { + Executor exec = null; + boolean resumed = false; synchronized (this) { running = false; + if (this.executor != null) { + //resume happened in the meantime + suspended = false; + exec = this.executor; + // prevent future suspensions from re-submitting the task + this.executor = null; + } else if (!suspended) { + resumed = true; + } } + if (exec != null) { + //outside sync block + exec.execute(this); + } else if (resumed) { + resume(); + } + } } } @@ -300,19 +304,21 @@ public H[] getHandlers() { * a response result and switch to the abort chain */ public void handleException(Throwable t) { - if (handlers == abortHandlerChain) { + if (abortHandlerChainStarted) { handleUnrecoverableError(unwrapException(t)); } else { this.throwable = unwrapException(t); + abortHandlerChainStarted = true; restart(abortHandlerChain); } } public void handleException(Throwable t, boolean keepSameTarget) { - if (handlers == abortHandlerChain) { + if (abortHandlerChainStarted) { handleUnrecoverableError(unwrapException(t)); } else { this.throwable = unwrapException(t); + abortHandlerChainStarted = true; restart(abortHandlerChain, keepSameTarget); } } @@ -380,4 +386,8 @@ public synchronized void registerConnectionCallback(ConnectionCallback callback) connectionCallbacks = new ArrayList<>(); connectionCallbacks.add(callback); } + + public void setAbortHandlerChainStarted(boolean value) { + abortHandlerChainStarted = value; + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java index 2d77b754d7f8f..cba84c1d8852a 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java @@ -1,6 +1,6 @@ package org.jboss.resteasy.reactive.server.core; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -31,7 +31,7 @@ public class DeploymentInfo { private ResteasyReactiveConfig config; private Function clientProxyUnwrapper; private String applicationPath; - private List globalHandlerCustomers = Collections.emptyList(); + private List globalHandlerCustomers = new ArrayList<>(); public ResourceInterceptors getInterceptors() { return interceptors; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index 0b699f4ace735..aaa97a3027348 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -183,11 +183,12 @@ public void restart(RuntimeResource target, boolean setLocatorTarget) { } /** - * Meant to be used when a error occurred early in processing chain + * Meant to be used when an error occurred early in processing chain */ @Override public void abortWith(Response response) { setResult(response); + setAbortHandlerChainStarted(true); restart(getAbortHandlerChain()); // this is a valid action after suspend, in which case we must resume if (isSuspended()) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java index ab4369160abba..1e2ec790529e1 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; @@ -25,7 +26,7 @@ public class FormEncodedDataDefinition implements FormParserFactory.ParserDefini private static final Logger log = Logger.getLogger(FormEncodedDataDefinition.class); public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; - private String defaultEncoding = "ISO-8859-1"; + private String defaultCharset = StandardCharsets.UTF_8.displayName();; private boolean forceCreation = false; //if the parser should be created even if the correct headers are missing private int maxParams = 1000; private long maxAttributeSize = 2048; @@ -38,7 +39,7 @@ public FormDataParser create(final ResteasyReactiveRequestContext exchange) { String mimeType = exchange.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); if (forceCreation || (mimeType != null && mimeType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED))) { - String charset = defaultEncoding; + String charset = defaultCharset; String contentType = exchange.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); if (contentType != null) { String cs = HeaderUtil.extractQuotedValueFromHeader(contentType, "charset"); @@ -52,8 +53,8 @@ public FormDataParser create(final ResteasyReactiveRequestContext exchange) { return null; } - public String getDefaultEncoding() { - return defaultEncoding; + public String getDefaultCharset() { + return defaultCharset; } public boolean isForceCreation() { @@ -83,8 +84,8 @@ public FormEncodedDataDefinition setForceCreation(boolean forceCreation) { return this; } - public FormEncodedDataDefinition setDefaultEncoding(final String defaultEncoding) { - this.defaultEncoding = defaultEncoding; + public FormEncodedDataDefinition setDefaultCharset(final String defaultCharset) { + this.defaultCharset = defaultCharset; return this; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java index 21324c6b8408e..c83442401d2b0 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java @@ -43,7 +43,7 @@ public interface ParserDefinition { FormDataParser create(final ResteasyReactiveRequestContext exchange); - T setDefaultEncoding(String charset); + T setDefaultCharset(String charset); } public static Builder builder(Supplier executorSupplier) { @@ -114,7 +114,7 @@ public Builder withDefaultCharset(String defaultCharset) { public FormParserFactory build() { if (defaultCharset != null) { for (ParserDefinition parser : parsers) { - parser.setDefaultEncoding(defaultCharset); + parser.setDefaultCharset(defaultCharset); } } return new FormParserFactory(parsers); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java index ec1e4fa875cfe..907013bf7d53c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java @@ -41,7 +41,7 @@ public class MultiPartParserDefinition implements FormParserFactory.ParserDefini private Path tempFileLocation; - private String defaultEncoding = StandardCharsets.ISO_8859_1.displayName(); + private String defaultCharset = StandardCharsets.UTF_8.displayName(); private boolean deleteUploadsOnEnd = true; @@ -49,7 +49,6 @@ public class MultiPartParserDefinition implements FormParserFactory.ParserDefini private long fileSizeThreshold; - private int maxParameters = 1000; private long maxAttributeSize = 2048; private long maxEntitySize = -1; @@ -75,7 +74,7 @@ public FormDataParser create(final ResteasyReactiveRequestContext exchange) { return null; } final MultiPartUploadHandler parser = new MultiPartUploadHandler(exchange, boundary, maxIndividualFileSize, - fileSizeThreshold, defaultEncoding, mimeType, maxAttributeSize, maxEntitySize); + fileSizeThreshold, defaultCharset, mimeType, maxAttributeSize, maxEntitySize); exchange.registerCompletionCallback(new CompletionCallback() { @Override public void onComplete(Throwable throwable) { @@ -119,12 +118,12 @@ public MultiPartParserDefinition setTempFileLocation(Path tempFileLocation) { return this; } - public String getDefaultEncoding() { - return defaultEncoding; + public String getDefaultCharset() { + return defaultCharset; } - public MultiPartParserDefinition setDefaultEncoding(final String defaultEncoding) { - this.defaultEncoding = defaultEncoding; + public MultiPartParserDefinition setDefaultCharset(final String defaultCharset) { + this.defaultCharset = defaultCharset; return this; } @@ -181,6 +180,7 @@ private MultiPartUploadHandler(final ResteasyReactiveRequestContext exchange, fi this.fileSizeThreshold = fileSizeThreshold; this.maxAttributeSize = maxAttributeSize; this.maxEntitySize = maxEntitySize; + int maxParameters = 1000; this.data = new FormData(maxParameters); String charset = defaultEncoding; if (contentType != null) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java index c104fdc54bab8..21214b9fec9ae 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java @@ -198,7 +198,8 @@ public BeanFactory.BeanInstance apply(Class aClass) { } for (int i = 0; i < info.getGlobalHandlerCustomizers().size(); i++) { preMatchHandlers - .addAll(info.getGlobalHandlerCustomizers().get(i).handlers(HandlerChainCustomizer.Phase.AFTER_PRE_MATCH)); + .addAll(info.getGlobalHandlerCustomizers().get(i).handlers(HandlerChainCustomizer.Phase.AFTER_PRE_MATCH, + null, null)); } return new Deployment(exceptionMapping, info.getCtxResolvers(), serialisers, diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index a28a1e1b7cca7..961c93ac02295 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -127,8 +127,6 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, MultivaluedMap score = new QuarkusMultivaluedHashMap<>(); Map pathParameterIndexes = buildParamIndexMap(classPathTemplate, methodPathTemplate); - List handlers = new ArrayList<>(); - addHandlers(handlers, clazz, method, info, HandlerChainCustomizer.Phase.AFTER_MATCH); MediaType sseElementType = null; if (method.getSseElementType() != null) { sseElementType = MediaType.valueOf(method.getSseElementType()); @@ -159,10 +157,13 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, .forMethod(method, lazyMethod); //setup reader and writer interceptors first - handlers.addAll(interceptorDeployment.setupInterceptorHandler()); - //at this point the handler chain only has interceptors - //which we also want in the abort handler chain - List abortHandlingChain = new ArrayList<>(handlers); + List interceptorHandlers = interceptorDeployment.setupInterceptorHandler(); + //we want interceptors in the abort handler chain + List abortHandlingChain = new ArrayList<>(interceptorHandlers); + + List handlers = new ArrayList<>(); + addHandlers(handlers, clazz, method, info, HandlerChainCustomizer.Phase.AFTER_MATCH); + handlers.addAll(interceptorHandlers); // when a method is blocking, we also want all the request filters to run on the worker thread // because they can potentially set thread local variables diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java index 5d6fcbf4b23eb..1478096c4238d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java @@ -24,8 +24,6 @@ public class FormBodyHandler implements ServerRestHandler, RuntimeConfigurableServerRestHandler { - private static final byte[] NO_BYTES = new byte[0]; - private final boolean alsoSetInputStream; private final Supplier executorSupplier; private volatile FormParserFactory formParserFactory; @@ -43,9 +41,12 @@ public void configure(RuntimeConfiguration configuration) { .setMaxAttributeSize(configuration.limits().maxFormAttributeSize()) .setMaxEntitySize(configuration.limits().maxBodySize().orElse(-1L)) .setDeleteUploadsOnEnd(configuration.body().deleteUploadedFilesOnEnd()) + .setDefaultCharset(configuration.body().defaultCharset().name()) .setTempFileLocation(Path.of(configuration.body().uploadsDirectory()))) + .addParser(new FormEncodedDataDefinition() - .setMaxAttributeSize(configuration.limits().maxFormAttributeSize())) + .setMaxAttributeSize(configuration.limits().maxFormAttributeSize()) + .setDefaultCharset(configuration.body().defaultCharset().name())) .build(); try { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ContainerRequestContextImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ContainerRequestContextImpl.java index cd565a16f80d8..0cf028c56f53f 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ContainerRequestContextImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ContainerRequestContextImpl.java @@ -181,6 +181,7 @@ public ContainerRequestContextImpl setPreMatch(boolean preMatch) { public void abortWith(Response response) { assertNotResponse(); quarkusRestContext.setResult(response); + quarkusRestContext.setAbortHandlerChainStarted(true); quarkusRestContext.restart(quarkusRestContext.getAbortHandlerChain(), true); aborted = true; // this is a valid action after suspend, in which case we must resume diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/HandlerChainCustomizer.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/HandlerChainCustomizer.java index d12bf6d0e4434..199e0da8249ee 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/HandlerChainCustomizer.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/HandlerChainCustomizer.java @@ -12,8 +12,7 @@ public interface HandlerChainCustomizer { /** * * @param phase The phase - * @param serverResourceMethod The method, will be null if this has not been matched yet - * @return + * @param resourceMethod The method, will be null if this has not been matched yet */ default List handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) { return handlers(phase); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/RuntimeConfiguration.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/RuntimeConfiguration.java index a60b187bc70d1..5bdd06534a537 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/RuntimeConfiguration.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/RuntimeConfiguration.java @@ -1,5 +1,6 @@ package org.jboss.resteasy.reactive.server.spi; +import java.nio.charset.Charset; import java.time.Duration; import java.util.Optional; @@ -16,6 +17,8 @@ interface Body { boolean deleteUploadedFilesOnEnd(); String uploadsDirectory(); + + Charset defaultCharset(); } interface Limits { diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java index 4178747cd4704..25934998c399c 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java @@ -308,7 +308,9 @@ public ServerHttpResponse setStatusCode(int code) { @Override public ServerHttpResponse end() { - response.end(); + if (!response.ended()) { + response.end(); + } return this; } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java index b14bd5b13e098..7d01a781d8294 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java @@ -176,6 +176,10 @@ public static RegistriesConfig toolsConfig() { return toolsConfig == null ? toolsConfig = RegistriesConfigLocator.resolveConfig() : toolsConfig; } + public static void setToolsConfig(RegistriesConfig config) { + toolsConfig = config; + } + public static void reset() { initRegistryClientEnabled(); toolsConfig = null; diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/ExtensionsAppearingInPlatformAndNonPlatformCatalogsTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/ExtensionsAppearingInPlatformAndNonPlatformCatalogsTest.java new file mode 100644 index 0000000000000..ee7aa12ad9bb8 --- /dev/null +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/ExtensionsAppearingInPlatformAndNonPlatformCatalogsTest.java @@ -0,0 +1,222 @@ +package io.quarkus.devtools.project.create; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkus.devtools.commands.CreateProject; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.QuarkusProjectHelper; +import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder; +import io.quarkus.maven.ArtifactCoords; +import io.quarkus.registry.catalog.ExtensionCatalog; +import io.quarkus.registry.catalog.ExtensionOrigin; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * The catalogs used in this test do not clearly separate platform from non-platform extensions. + * Which should not be happening in the reality but still may happen because of an oversight or something. + * So this test makes sure the code doesn't fail in an unreasonable way at least. + */ +public class ExtensionsAppearingInPlatformAndNonPlatformCatalogsTest extends MultiplePlatformBomsTestBase { + + private static final String MAIN_PLATFORM_KEY = "org.acme.platform"; + + @BeforeAll + public static void setup() throws Exception { + TestRegistryClientBuilder.newInstance() + //.debug() + .baseDir(configDir()) + // registry + .newRegistry("registry.acme.org") + // platform key + .newPlatform(MAIN_PLATFORM_KEY) + // 2.0 STREAM + .newStream("2.0") + // 2.0.4 release + .newRelease("2.0.4") + .quarkusVersion("2.2.2") + // default bom including quarkus-core + essential metadata + .addCoreMember() + // foo platform member + .newMember("acme-foo-bom").addExtension("acme-foo").addExtension("org.other", "other-extension", "6.0") + .release() + .stream().platform() + // 1.0 STREAM + .newStream("1.0") + // 1.0.1 release + .newRelease("1.0.1") + .quarkusVersion("1.1.2") + .addCoreMember() + .newMember("acme-foo-bom").addExtension("acme-foo").addExtension("org.other", "other-extension", "5.1") + .release() + .newMember("acme-baz-bom").addExtension("acme-baz").release() + .stream() + // 1.0.0 release + .newRelease("1.0.0") + .quarkusVersion("1.1.1") + .addCoreMember() + .newMember("acme-foo-bom").addExtension("acme-foo").addExtension("org.other", "other-extension", "5.0") + .release() + .newMember("acme-bar-bom").addExtension("acme-bar").release() + .newMember("acme-baz-bom").addExtension("acme-baz").release() + .registry() + // NON-PLATFORM EXTENSION CATALOG FOR QUARKUS 2.2.2 + .newNonPlatformCatalog("2.2.2") + .addExtension("org.other", "other-extension", "6.0") + .addExtension("org.other", "other-six-zero", "6.0") + .registry() + // NON-PLATFORM EXTENSION CATALOG FOR QUARKUS 1.1.2 + .newNonPlatformCatalog("1.1.2") + .addExtension("org.other", "other-extension", "5.1") + .addExtension("org.other", "other-five-one", "5.1") + .registry() + // NON-PLATFORM EXTENSION CATALOG FOR QUARKUS 1.1.1 + .newNonPlatformCatalog("1.1.1") + .addExtension("org.other", "other-extension", "5.0") + .addExtension("org.other", "other-five-zero", "5.0") + .registry() + .clientBuilder() + .build(); + + enableRegistryClient(); + } + + protected String getMainPlatformKey() { + return MAIN_PLATFORM_KEY; + } + + @Test + public void createWithPreferedCatalogs() throws Exception { + final Path projectDir = newProjectDir("preferred-catalogs"); + + final ExtensionCatalog catalog = QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog(Arrays.asList( + ArtifactCoords.fromString("org.acme.platform:quarkus-bom::pom:1.0.1"), + ArtifactCoords.fromString("org.acme.platform:acme-foo-bom::pom:1.0.1"), + ArtifactCoords.fromString("org.acme.platform:acme-baz-bom::pom:1.0.1"))); + final QuarkusProject project = QuarkusProjectHelper.getProject(projectDir, catalog, BuildTool.MAVEN); + + final Set extensionKeys = new HashSet<>(); + final List expectedExtensions = new ArrayList<>(); + catalog.getExtensions().forEach(e -> { + final ArtifactCoords coords = e.getArtifact(); + extensionKeys.add(coords.getGroupId() + ":" + coords.getArtifactId()); + boolean platform = false; + for (ExtensionOrigin o : e.getOrigins()) { + if (o.isPlatform()) { + platform = true; + break; + } + } + expectedExtensions.add(platform + ? new ArtifactCoords(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getType(), + null) + : coords); + }); + new CreateProject(project).extensions(extensionKeys).noCode().execute(); + + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom", "acme-baz-bom"), expectedExtensions, "1.0.1"); + } + + @Test + public void createWithNonPlatformExtensionCompatibleWithTheOldestQuarkusVersion() throws Exception { + final Path projectDir = newProjectDir("non-platform-extension-oldest-quarkus-version"); + createProject(projectDir, Arrays.asList("acme-foo", "other-extension", "other-five-zero")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + expectedExtensions.add(ArtifactCoords.fromString("org.other:other-five-zero:5.0")); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom"), expectedExtensions, "1.0.0"); + } + + @Test + public void createWithNonPlatformExtensionCompatibleWithTheLatestQuarkusVersion() throws Exception { + final Path projectDir = newProjectDir("non-platform-extension-latest-quarkus-version"); + createProject(projectDir, Arrays.asList("acme-foo", "other-extension", "other-six-zero")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + expectedExtensions.add(ArtifactCoords.fromString("org.other:other-six-zero:6.0")); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom"), expectedExtensions, "2.0.4"); + } + + @Test + public void createWithNonPlatformExtensionCompatibleWithQuarkusVersion112() throws Exception { + final Path projectDir = newProjectDir("non-platform-extension-quarkus-version-112"); + createProject(projectDir, Arrays.asList("acme-foo", "other-extension", "other-five-one")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + expectedExtensions.add(ArtifactCoords.fromString("org.other:other-five-one:5.1")); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom"), expectedExtensions, "1.0.1"); + } + + @Test + public void createWithExtensionsPresentInTheLatestReleaseOfTheLatestStream() throws Exception { + final Path projectDir = newProjectDir("latest-release-latest-stream"); + createProject(projectDir, Arrays.asList("acme-foo", "other-extension")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom"), expectedExtensions, "2.0.4"); + } + + @Test + public void createWithExtensionsPresentInTheLatestReleaseOfAnOldStream() throws Exception { + final Path projectDir = newProjectDir("latest-release-old-stream"); + createProject(projectDir, Arrays.asList("acme-baz", "acme-foo", "other-extension")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo", "acme-baz"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom", "acme-baz-bom"), expectedExtensions, "1.0.1"); + } + + @Test + public void createWithExtensionPresentInTheOldReleaseOfAnOldStream() throws Exception { + final Path projectDir = newProjectDir("old-release-old-stream"); + createProject(projectDir, Arrays.asList("acme-bar", "acme-foo", "other-extension")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-foo", "acme-bar"); + expectedExtensions.add(new ArtifactCoords("org.other", "other-extension", null)); + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom", "acme-bar-bom"), + expectedExtensions, "1.0.0"); + } + + @Test + public void addExtensionAndImportMemberBom() throws Exception { + final Path projectDir = newProjectDir("add-extension-import-bom"); + createProject(projectDir, Arrays.asList("other-five-one")); + + assertModel(projectDir, toPlatformBomCoords(), Arrays.asList(ArtifactCoords.fromString("org.other:other-five-one:5.1")), + "1.0.1"); + + addExtensions(projectDir, Arrays.asList("acme-baz")); + + final List expectedExtensions = toPlatformExtensionCoords("acme-baz"); + expectedExtensions.add(ArtifactCoords.fromString("org.other:other-five-one:5.1")); + assertModel(projectDir, toPlatformBomCoords("acme-baz-bom"), expectedExtensions, "1.0.1"); + } + + @Test + public void attemptCreateWithIncompatibleExtensions() throws Exception { + final Path projectDir = newProjectDir("create-with-incompatible-extensions"); + assertThat(createProject(projectDir, Arrays.asList("acme-bar", "other-five-one")).isSuccess()).isFalse(); + } + + @Test + public void attemptAddingExtensionFromIncompatibleMemberBom() throws Exception { + final Path projectDir = newProjectDir("add-extension-incompatible-member-bom"); + createProject(projectDir, Arrays.asList("other-five-one")); + + assertModel(projectDir, toPlatformBomCoords(), Arrays.asList(ArtifactCoords.fromString("org.other:other-five-one:5.1")), + "1.0.1"); + + assertThat(addExtensions(projectDir, Arrays.asList("acme-bar")).isSuccess()).isFalse(); + } +} diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MavenProjectImportingMultipleBomsFromSinglePlatformTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MavenProjectImportingMultipleBomsFromSinglePlatformTest.java index d3fc0807f01c8..9cbfb89322a53 100644 --- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MavenProjectImportingMultipleBomsFromSinglePlatformTest.java +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MavenProjectImportingMultipleBomsFromSinglePlatformTest.java @@ -2,11 +2,20 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.quarkus.devtools.commands.CreateProject; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder; import io.quarkus.maven.ArtifactCoords; +import io.quarkus.registry.catalog.ExtensionCatalog; +import io.quarkus.registry.catalog.ExtensionOrigin; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -75,6 +84,38 @@ protected String getMainPlatformKey() { return MAIN_PLATFORM_KEY; } + @Test + public void createWithPreferedCatalogs() throws Exception { + final Path projectDir = newProjectDir("preferred-catalogs"); + + final ExtensionCatalog catalog = QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog(Arrays.asList( + ArtifactCoords.fromString("org.acme.platform:quarkus-bom::pom:1.0.1"), + ArtifactCoords.fromString("org.acme.platform:acme-foo-bom::pom:1.0.1"), + ArtifactCoords.fromString("org.acme.platform:acme-baz-bom::pom:1.0.1"))); + final QuarkusProject project = QuarkusProjectHelper.getProject(projectDir, catalog, BuildTool.MAVEN); + + final Set extensionKeys = new HashSet<>(); + final List expectedExtensions = new ArrayList<>(); + catalog.getExtensions().forEach(e -> { + final ArtifactCoords coords = e.getArtifact(); + extensionKeys.add(coords.getGroupId() + ":" + coords.getArtifactId()); + boolean platform = false; + for (ExtensionOrigin o : e.getOrigins()) { + if (o.isPlatform()) { + platform = true; + break; + } + } + expectedExtensions.add(platform + ? new ArtifactCoords(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getType(), + null) + : coords); + }); + new CreateProject(project).extensions(extensionKeys).noCode().execute(); + + assertModel(projectDir, toPlatformBomCoords("acme-foo-bom", "acme-baz-bom"), expectedExtensions, "1.0.1"); + } + @Test public void createWithNonPlatformExtensionCompatibleWithTheOldestQuarkusVersion() throws Exception { final Path projectDir = newProjectDir("non-platform-extension-oldest-quarkus-version"); diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/selection/OriginSelector.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/selection/OriginSelector.java index 9eb4cfd06a913..9250b7a45a50c 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/selection/OriginSelector.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/selection/OriginSelector.java @@ -21,7 +21,7 @@ public void calculateCompatibleCombinations() { select(0, new OriginCombination()); // log all the complete combincations - //System.out.println("OriginSelector.calculateCompatibleCombinations of " + extOrigins.size() + " extensions"); + //System.out.println("OriginSelector.calculateCompatibleCombinations of " + extOrigins.size() + " extensions, complete combinations: " + completeCombinations.size()); //if (completeCombinations.isEmpty()) { // System.out.println(" none"); //} else { @@ -33,7 +33,6 @@ public void calculateCompatibleCombinations() { // " - " + o.getCatalog().getBom() + " " + o.getCatalog().isPlatform() + " " + o.getPreference())); // } //} - } public OriginCombination getRecommendedCombination() { @@ -62,7 +61,7 @@ private double calculateScore(OriginCombination s) { for (OriginWithPreference o : s.getCollectedOrigins()) { combinationScore += Math.pow(extOrigins.size(), highestRegistryPreference + 1 - o.getPreference().registryPreference) - * ((double) (Integer.MAX_VALUE + 1 - o.getPreference().platformPreference) / Integer.MAX_VALUE); + * ((((double) Integer.MAX_VALUE) + 1 - o.getPreference().platformPreference) / Integer.MAX_VALUE); } return combinationScore; } diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java index 156166f6693a4..6e24e44c8e302 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/RegistriesConfigLocator.java @@ -105,6 +105,11 @@ public static RegistriesConfig load(Reader configYaml) { } } + /** + * Returns the registry client configuration file or null, if the file could not be found. + * + * @return the registry client configuration file or null, if the file could not be found + */ public static Path locateConfigYaml() { final String prop = PropertiesUtil.getProperty(CONFIG_FILE_PATH_PROPERTY); Path configYaml; @@ -120,10 +125,19 @@ public static Path locateConfigYaml() { if (Files.exists(configYaml)) { return configYaml; } - configYaml = Paths.get(PropertiesUtil.getProperty("user.home")).resolve(CONFIG_RELATIVE_PATH); + configYaml = getDefaultConfigYamlLocation(); return Files.exists(configYaml) ? configYaml : null; } + /** + * Returns the default location of the registry client configuration file. + * + * @return the default location of the registry client configuration file + */ + public static Path getDefaultConfigYamlLocation() { + return Paths.get(PropertiesUtil.getProperty("user.home")).resolve(CONFIG_RELATIVE_PATH); + } + static RegistriesConfig completeRequiredConfig(RegistriesConfig original) { final JsonRegistriesConfig config = new JsonRegistriesConfig(); config.setDebug(original.isDebug()); diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java index bffce7073d8e9..2f88b7b183445 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistriesConfig.java @@ -7,7 +7,6 @@ import io.quarkus.registry.config.RegistriesConfig; import io.quarkus.registry.config.RegistryConfig; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -15,7 +14,7 @@ public class JsonRegistriesConfig implements RegistriesConfig { private boolean debug; - private List registries = Collections.emptyList(); + private List registries = new ArrayList<>(); @Override @JsonDeserialize(contentUsing = JsonRegistryConfigDeserializer.class) @@ -25,13 +24,14 @@ public List getRegistries() { } public void setRegistries(List registries) { - this.registries = registries == null ? Collections.emptyList() : registries; + if (registries == null) { + this.registries.clear(); + } else { + this.registries = registries; + } } public void addRegistry(RegistryConfig registry) { - if (registries.isEmpty()) { - registries = new ArrayList<>(); - } registries.add(registry); } diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java index 42f51409cd25f..0f8c5ddc39447 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/config/json/JsonRegistryConfigSerializer.java @@ -19,7 +19,8 @@ public class JsonRegistryConfigSerializer extends JsonSerializer maven-invoker-way quarkus-standard-way + quarkus-standard-way-kafka diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/pom.xml b/integration-tests/kubernetes/quarkus-standard-way-kafka/pom.xml new file mode 100644 index 0000000000000..a43d7649c9c63 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/pom.xml @@ -0,0 +1,227 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-test-kubernetes-parent + 999-SNAPSHOT + + + quarkus-integration-test-kubernetes-standard-kafka + Quarkus - Integration Tests - Kubernetes - Standard - Kafka + Kubernetes integration tests that use @QuarkusProdModeTest for dummy Kafka messaing only application + + + + + quarkus-container-image-docker-deployment, + quarkus-container-image-jib-deployment, + quarkus-container-image-openshift-deployment, + quarkus-container-image-s2i-deployment + + + + + + io.quarkus + quarkus-smallrye-reactive-messaging-kafka + + + io.quarkus + quarkus-kubernetes + + + io.quarkus + quarkus-junit5-internal + test + + + org.assertj + assertj-core + test + + + io.fabric8 + kubernetes-model + test + + + javax.annotation + javax.annotation-api + + + javax.xml.bind + jaxb-api + + + jakarta.xml.bind + jakarta.xml.bind-api + + + + + io.fabric8 + knative-model + test + + + javax.annotation + javax.annotation-api + + + javax.xml.bind + jaxb-api + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + test + + + javax.annotation + javax.annotation-api + + + javax.xml.bind + jaxb-api + + + + + jakarta.annotation + jakarta.annotation-api + test + + + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + + + io.quarkus + quarkus-kubernetes-spi + test + + + * + * + + + + + + + io.quarkus + quarkus-smallrye-reactive-messaging-kafka-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-kubernetes-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-container-image-docker-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-container-image-jib-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-container-image-openshift-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-container-image-s2i-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + basic-test-suite + + + basicTests + + + + true + + + + + diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/main/java/io/quarkus/it/kubernetes/kafka/DummyProcessor.java b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/main/java/io/quarkus/it/kubernetes/kafka/DummyProcessor.java new file mode 100644 index 0000000000000..edc846d2eafad --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/main/java/io/quarkus/it/kubernetes/kafka/DummyProcessor.java @@ -0,0 +1,22 @@ +package io.quarkus.it.kubernetes.kafka; + +import java.util.Random; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import io.smallrye.common.annotation.Blocking; + +public class DummyProcessor { + + private final Random random = new Random(); + + @Incoming("requests") + @Outgoing("quotes") + @Blocking + public int process(String quoteRequest) throws InterruptedException { + // simulate some hard working task + Thread.sleep(200); + return random.nextInt(100); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicKubernetesTest.java b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicKubernetesTest.java new file mode 100644 index 0000000000000..3a4450954f6ae --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicKubernetesTest.java @@ -0,0 +1,68 @@ +package io.quarkus.it.kubernetes.kafka; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +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.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class BasicKubernetesTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(DummyProcessor.class)) + .setApplicationName("basic") + .setApplicationVersion("0.1-SNAPSHOT") + .withConfigurationResource("basic-kubernetes.properties"); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")) + .satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2)); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + + assertThat(kubernetesList).hasSize(1); + + assertThat(kubernetesList.get(0)).isInstanceOfSatisfying(Deployment.class, d -> { + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo("basic"); + assertThat(m.getNamespace()).isNull(); + }); + + assertThat(d.getSpec()).satisfies(deploymentSpec -> { + assertThat(deploymentSpec.getSelector()).isNotNull().satisfies(labelSelector -> { + assertThat(labelSelector.getMatchLabels()).containsOnly(entry("app.kubernetes.io/name", "basic"), + entry("app.kubernetes.io/version", "0.1-SNAPSHOT")); + }); + + assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { + assertThat(container.getImagePullPolicy()).isEqualTo("Always"); + assertThat(container.getPorts()).isNullOrEmpty(); + }); + }); + }); + }); + }); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicMinikubeTest.java b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicMinikubeTest.java new file mode 100644 index 0000000000000..41d24d358aee4 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicMinikubeTest.java @@ -0,0 +1,66 @@ +package io.quarkus.it.kubernetes.kafka; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +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.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class BasicMinikubeTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(DummyProcessor.class)) + .setApplicationName("minikube-with-defaults") + .setApplicationVersion("0.1-SNAPSHOT") + .withConfigurationResource("basic-minikube.properties") + .setForcedDependencies( + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("minikube.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("minikube.yml")) + .satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2)); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("minikube.yml")); + + assertThat(kubernetesList).singleElement().isInstanceOfSatisfying(Deployment.class, d -> { + + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getNamespace()).isNull(); + }); + + assertThat(d.getSpec()).satisfies(deploymentSpec -> { + assertThat(deploymentSpec.getReplicas()).isEqualTo(1); + assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { + assertThat(container.getImagePullPolicy()).isEqualTo("IfNotPresent"); + assertThat(container.getPorts()).isNullOrEmpty(); + }); + }); + }); + }); + }); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicOpenshiftTest.java b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicOpenshiftTest.java new file mode 100644 index 0000000000000..d907871ba898e --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/BasicOpenshiftTest.java @@ -0,0 +1,71 @@ +package io.quarkus.it.kubernetes.kafka; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.assertj.core.api.AbstractObjectAssert; +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.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.PodTemplateSpec; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class BasicOpenshiftTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(DummyProcessor.class)) + .setApplicationName("basic-openshift") + .setApplicationVersion("0.1-SNAPSHOT") + .withConfigurationResource("basic-openshift.properties"); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @SuppressWarnings("unchecked") + @Test + public void assertGeneratedResources() throws IOException { + Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("openshift.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("openshift.yml")) + .satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2)); + List openshiftList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("openshift.yml")); + + assertThat(openshiftList).filteredOn(h -> "DeploymentConfig".equals(h.getKind())).singleElement().satisfies(h -> { + assertThat(h.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo("basic-openshift"); + assertThat(m.getLabels().get("app.openshift.io/runtime")).isEqualTo("quarkus"); + assertThat(m.getNamespace()).isNull(); + }); + AbstractObjectAssert specAssert = assertThat(h).extracting("spec"); + specAssert.extracting("replicas").isEqualTo(1); + specAssert.extracting("triggers").isInstanceOfSatisfying(Collection.class, c -> { + assertThat(c).isEmpty(); + }); + specAssert.extracting("selector").isInstanceOfSatisfying(Map.class, selectorsMap -> { + assertThat(selectorsMap).containsOnly(entry("app.kubernetes.io/name", "basic-openshift"), + entry("app.kubernetes.io/version", "0.1-SNAPSHOT")); + }); + specAssert.extracting("template").isInstanceOfSatisfying(PodTemplateSpec.class, templateMap -> { + assertThat(templateMap.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { + assertThat(container.getPorts()).isNullOrEmpty(); + }); + }); + }); + }); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/DeserializationUtil.java b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/DeserializationUtil.java new file mode 100644 index 0000000000000..c86e3eb1f3b0b --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/java/io/quarkus/it/kubernetes/kafka/DeserializationUtil.java @@ -0,0 +1,65 @@ +package io.quarkus.it.kubernetes.kafka; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; + +import io.fabric8.kubernetes.api.model.HasMetadata; + +// TODO: as this is a copy of the class in the 'quarkus-integration-test-kubernetes-standard', +// maybe we should create a new kubernetes test module to contain this class? +final class DeserializationUtil { + + private static final String DOCUMENT_DELIMITER = "---"; + static final ObjectMapper MAPPER = new ObjectMapper(new YAMLFactory()); + + private DeserializationUtil() { + } + + /** + * Takes a YAML file as input and output a list of Kubernetes resources + * present in the file + * The list is sorted alphabetically based on the resource Kind + */ + public static List deserializeAsList(Path yamlFilePath) throws IOException { + String[] parts = splitDocument(Files.readAllLines(yamlFilePath, StandardCharsets.UTF_8).toArray(new String[0])); + List items = new ArrayList<>(); + for (String part : parts) { + if (part.trim().isEmpty()) { + continue; + } + items.add(MAPPER.readValue(part, HasMetadata.class)); + } + items.sort(Comparator.comparing(HasMetadata::getKind)); + return items; + } + + static String[] splitDocument(String[] lines) { + List documents = new ArrayList<>(); + int nLine = 0; + StringBuilder builder = new StringBuilder(); + + while (nLine < lines.length) { + if (lines[nLine].length() < DOCUMENT_DELIMITER.length() + || !lines[nLine].substring(0, DOCUMENT_DELIMITER.length()).equals(DOCUMENT_DELIMITER)) { + builder.append(lines[nLine]).append(System.lineSeparator()); + } else { + documents.add(builder.toString()); + builder.setLength(0); + } + + nLine++; + } + + if (!builder.toString().isEmpty()) + documents.add(builder.toString()); + return documents.toArray(new String[0]); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-kubernetes.properties b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-kubernetes.properties new file mode 100644 index 0000000000000..f7c694380a316 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-kubernetes.properties @@ -0,0 +1,7 @@ +# Configure the incoming `quote-requests` Kafka topic +mp.messaging.incoming.requests.connector=smallrye-kafka +mp.messaging.incoming.requests.topic=quote-requests +mp.messaging.incoming.requests.auto.offset.reset=earliest + +# Configure the outgoing `quotes` Kafka topic +mp.messaging.outgoing.quotes.connector=smallrye-kafka diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-minikube.properties b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-minikube.properties new file mode 100644 index 0000000000000..c81f822dee726 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-minikube.properties @@ -0,0 +1,9 @@ +quarkus.kubernetes.deployment-target=minikube + +# Configure the incoming `quote-requests` Kafka topic +mp.messaging.incoming.requests.connector=smallrye-kafka +mp.messaging.incoming.requests.topic=quote-requests +mp.messaging.incoming.requests.auto.offset.reset=earliest + +# Configure the outgoing `quotes` Kafka topic +mp.messaging.outgoing.quotes.connector=smallrye-kafka diff --git a/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-openshift.properties b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-openshift.properties new file mode 100644 index 0000000000000..152bb28d271af --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way-kafka/src/test/resources/basic-openshift.properties @@ -0,0 +1,9 @@ +quarkus.kubernetes.deployment-target=openshift + +# Configure the incoming `quote-requests` Kafka topic +mp.messaging.incoming.requests.connector=smallrye-kafka +mp.messaging.incoming.requests.topic=quote-requests +mp.messaging.incoming.requests.auto.offset.reset=earliest + +# Configure the outgoing `quotes` Kafka topic +mp.messaging.outgoing.quotes.connector=smallrye-kafka diff --git a/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/host b/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/host index 2fbb50c4a8dc7..853f007dbfdb3 100644 --- a/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/host +++ b/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/host @@ -1 +1 @@ -localhost +localhost:37017 \ No newline at end of file diff --git a/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/port b/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/port deleted file mode 100644 index 72ae75d500d06..0000000000000 --- a/integration-tests/mongodb-rest-data-panache/src/test/resources/k8s-sb/mongo/port +++ /dev/null @@ -1 +0,0 @@ -37017 diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetOuterMockitoMocksCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetOuterMockitoMocksCallback.java new file mode 100644 index 0000000000000..ffbf7c0b35d82 --- /dev/null +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetOuterMockitoMocksCallback.java @@ -0,0 +1,14 @@ +package io.quarkus.test.junit.mockito.internal; + +import io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback; +import io.quarkus.test.junit.callback.QuarkusTestContext; + +public class ResetOuterMockitoMocksCallback implements QuarkusTestAfterAllCallback { + + @Override + public void afterAll(QuarkusTestContext context) { + if (context.getOuterInstance() != null) { + MockitoMocksTracker.reset(context.getOuterInstance()); + } + } +} diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SetMockitoMockAsBeanMockCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SetMockitoMockAsBeanMockCallback.java index 354a0d28378cc..3bbced045c6cb 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SetMockitoMockAsBeanMockCallback.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SetMockitoMockAsBeanMockCallback.java @@ -9,6 +9,9 @@ public class SetMockitoMockAsBeanMockCallback implements QuarkusTestBeforeEachCa @Override public void beforeEach(QuarkusTestMethodContext context) { MockitoMocksTracker.getMocks(context.getTestInstance()).forEach(this::installMock); + if (context.getOuterInstance() != null) { + MockitoMocksTracker.getMocks(context.getOuterInstance()).forEach(this::installMock); + } } private void installMock(MockitoMocksTracker.Mocked mocked) { diff --git a/test-framework/junit5-mockito/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback b/test-framework/junit5-mockito/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback new file mode 100644 index 0000000000000..e512f51b2f465 --- /dev/null +++ b/test-framework/junit5-mockito/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback @@ -0,0 +1 @@ +io.quarkus.test.junit.mockito.internal.ResetOuterMockitoMocksCallback diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 3d055df6962a0..9858d8e8228a6 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -108,10 +108,12 @@ import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.common.http.TestHTTPResourceManager; import io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer; +import io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback; import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback; import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback; import io.quarkus.test.junit.callback.QuarkusTestBeforeClassCallback; import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback; +import io.quarkus.test.junit.callback.QuarkusTestContext; import io.quarkus.test.junit.callback.QuarkusTestMethodContext; import io.quarkus.test.junit.internal.DeepClone; import io.quarkus.test.junit.internal.SerializationWithXStreamFallbackDeepClone; @@ -131,6 +133,8 @@ public class QuarkusTestExtension private static Class actualTestClass; private static Object actualTestInstance; + // needed for @Nested + private static Object outerInstance; private static ClassLoader originalCl; private static RunningQuarkusApplication runningQuarkusApplication; private static Pattern clonePattern; @@ -138,9 +142,9 @@ public class QuarkusTestExtension private static List beforeClassCallbacks; private static List afterConstructCallbacks; - private static List legacyAfterConstructCallbacks; private static List beforeEachCallbacks; private static List afterEachCallbacks; + private static List afterAllCallbacks; private static Class quarkusTestMethodContextClass; private static Class quarkusTestProfile; private static boolean hasPerTestResources; @@ -473,9 +477,9 @@ private void populateCallbacks(ClassLoader classLoader) throws ClassNotFoundExce quarkusTestMethodContextClass = null; beforeClassCallbacks = new ArrayList<>(); afterConstructCallbacks = new ArrayList<>(); - legacyAfterConstructCallbacks = new ArrayList<>(); beforeEachCallbacks = new ArrayList<>(); afterEachCallbacks = new ArrayList<>(); + afterAllCallbacks = new ArrayList<>(); ServiceLoader quarkusTestBeforeClassLoader = ServiceLoader .load(Class.forName(QuarkusTestBeforeClassCallback.class.getName(), false, classLoader), classLoader); @@ -497,6 +501,11 @@ private void populateCallbacks(ClassLoader classLoader) throws ClassNotFoundExce for (Object quarkusTestAfterEach : quarkusTestAfterEachLoader) { afterEachCallbacks.add(quarkusTestAfterEach); } + ServiceLoader quarkusTestAfterAllLoader = ServiceLoader + .load(Class.forName(QuarkusTestAfterAllCallback.class.getName(), false, classLoader), classLoader); + for (Object quarkusTestAfterAll : quarkusTestAfterAllLoader) { + afterAllCallbacks.add(quarkusTestAfterAll); + } } private void populateTestMethodInvokers(ClassLoader quarkusClassLoader) { @@ -642,9 +651,9 @@ public void afterEach(ExtensionContext context) throws Exception { throw new RuntimeException("Could not find method " + originalTestMethod + " on test class"); } - Constructor constructor = quarkusTestMethodContextClass.getConstructor(Object.class, Method.class); + Constructor constructor = quarkusTestMethodContextClass.getConstructor(Object.class, Object.class, Method.class); return new AbstractMap.SimpleEntry<>(quarkusTestMethodContextClass, - constructor.newInstance(actualTestInstance, actualTestMethod)); + constructor.newInstance(actualTestInstance, outerInstance, actualTestMethod)); } private boolean isNativeOrIntegrationTest(Class clazz) { @@ -851,12 +860,13 @@ private void initTestState(ExtensionContext extensionContext, ExtensionState sta Class previousActualTestClass = actualTestClass; actualTestClass = Class.forName(extensionContext.getRequiredTestClass().getName(), true, Thread.currentThread().getContextClassLoader()); + outerInstance = null; if (extensionContext.getRequiredTestClass().isAnnotationPresent(Nested.class)) { - Class parent = actualTestClass.getEnclosingClass(); - Object parentInstance = runningQuarkusApplication.instance(parent); - Constructor declaredConstructor = actualTestClass.getDeclaredConstructor(parent); + Class outerClass = actualTestClass.getEnclosingClass(); + outerInstance = runningQuarkusApplication.instance(outerClass); + Constructor declaredConstructor = actualTestClass.getDeclaredConstructor(outerClass); declaredConstructor.setAccessible(true); - actualTestInstance = declaredConstructor.newInstance(parentInstance); + actualTestInstance = declaredConstructor.newInstance(outerInstance); } else { actualTestInstance = runningQuarkusApplication.instance(actualTestClass); } @@ -870,9 +880,11 @@ private void initTestState(ExtensionContext extensionContext, ExtensionState sta afterConstructCallback.getClass().getMethod("afterConstruct", Object.class).invoke(afterConstructCallback, actualTestInstance); } - for (Object legacyAfterConstructCallback : legacyAfterConstructCallbacks) { - legacyAfterConstructCallback.getClass().getMethod("beforeAll", Object.class) - .invoke(legacyAfterConstructCallback, actualTestInstance); + if (outerInstance != null) { + for (Object afterConstructCallback : afterConstructCallbacks) { + afterConstructCallback.getClass().getMethod("afterConstruct", Object.class).invoke(afterConstructCallback, + outerInstance); + } } } catch (Exception e) { throw new TestInstantiationException("Failed to create test instance", e); @@ -1108,6 +1120,7 @@ private Method determineTCCLExtensionMethod(ReflectiveInvocationContext @Override public void afterAll(ExtensionContext context) throws Exception { resetHangTimeout(); + runAfterAllCallbacks(context); try { if (!isNativeOrIntegrationTest(context.getRequiredTestClass()) && (runningQuarkusApplication != null)) { popMockContext(); @@ -1117,6 +1130,31 @@ public void afterAll(ExtensionContext context) throws Exception { } } finally { currentTestClassStack.pop(); + outerInstance = null; + } + } + + private void runAfterAllCallbacks(ExtensionContext context) throws Exception { + if (isNativeOrIntegrationTest(context.getRequiredTestClass())) { + return; + } + if (afterAllCallbacks.isEmpty()) { + return; + } + + Class quarkusTestContextClass = Class.forName(QuarkusTestContext.class.getName(), true, + runningQuarkusApplication.getClassLoader()); + Object quarkusTestContextInstance = quarkusTestContextClass.getConstructor(Object.class, Object.class) + .newInstance(actualTestInstance, outerInstance); + + ClassLoader original = setCCL(runningQuarkusApplication.getClassLoader()); + try { + for (Object afterAllCallback : afterAllCallbacks) { + afterAllCallback.getClass().getMethod("afterAll", quarkusTestContextClass) + .invoke(afterAllCallback, quarkusTestContextInstance); + } + } finally { + setCCL(original); } } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestAfterAllCallback.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestAfterAllCallback.java new file mode 100644 index 0000000000000..867ac995c839c --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestAfterAllCallback.java @@ -0,0 +1,11 @@ +package io.quarkus.test.junit.callback; + +/** + * Can be implemented by classes that shall be called after all test methods in a {@code @QuarkusTest} have been run. + *

+ * The implementing class has to be {@linkplain java.util.ServiceLoader deployed as service provider on the class path}. + */ +public interface QuarkusTestAfterAllCallback { + + void afterAll(QuarkusTestContext context); +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestContext.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestContext.java new file mode 100644 index 0000000000000..8bcb72b69a3fa --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestContext.java @@ -0,0 +1,23 @@ +package io.quarkus.test.junit.callback; + +/** + * Context object passed to {@link QuarkusTestAfterAllCallback} + */ +public class QuarkusTestContext { + + private final Object testInstance; + private final Object outerInstance; + + public QuarkusTestContext(Object testInstance, Object outerInstance) { + this.testInstance = testInstance; + this.outerInstance = outerInstance; + } + + public Object getTestInstance() { + return testInstance; + } + + public Object getOuterInstance() { + return outerInstance; + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestMethodContext.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestMethodContext.java index 98f8a5ff92389..6062cf8ae4982 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestMethodContext.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/callback/QuarkusTestMethodContext.java @@ -5,20 +5,15 @@ /** * Context object passed to {@link QuarkusTestBeforeEachCallback} and {@link QuarkusTestAfterEachCallback} */ -public final class QuarkusTestMethodContext { +public final class QuarkusTestMethodContext extends QuarkusTestContext { - private final Object testInstance; private final Method testMethod; - public QuarkusTestMethodContext(Object testInstance, Method testMethod) { - this.testInstance = testInstance; + public QuarkusTestMethodContext(Object testInstance, Object outerInstance, Method testMethod) { + super(testInstance, outerInstance); this.testMethod = testMethod; } - public Object getTestInstance() { - return testInstance; - } - public Method getTestMethod() { return testMethod; }