From 272e1236884e4f3bf11957d94e01150435a46567 Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Sun, 5 May 2024 19:06:48 +0200 Subject: [PATCH 01/16] Fix correct parsing of collections in AmazonLambdaRecorder with CollectionInputReader add test for abstractHandler add test for collection input and collection output rename tests for clarification what they are for (cherry picked from commit df27b36a40031838b08b8890a0ff99168b13fd5c) --- ...stractInputCollectionOutputCollection.java | 20 ++++++++ ...tCollectionOutputCollectionLambdaImpl.java | 5 ++ ...lectionOutputCollectionLambdaImplTest.java | 46 +++++++++++++++++++ .../deployment/testing/GreetingLambda.java | 6 ++- .../testing/GreetingLambdaTest.java | 38 +++++++++++++++ ...InputCollectionOutputCollectionLambda.java | 24 ++++++++++ ...tCollectionOutputCollectionLambdaTest.java | 45 ++++++++++++++++++ ...aDevServicesContinuousTestingTestCase.java | 8 ++-- .../deployment/testing/LambdaHandlerET.java | 31 ------------- .../lambda/deployment/testing/Person.java | 15 ------ .../deployment/testing/model/InputPerson.java | 22 +++++++++ .../testing/model/OutputPerson.java | 20 ++++++++ .../lambda/runtime/AmazonLambdaRecorder.java | 6 +++ .../handlers/CollectionInputReader.java | 28 +++++++++++ 14 files changed, 263 insertions(+), 51 deletions(-) create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollection.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImpl.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImplTest.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambdaTest.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambdaTest.java delete mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java delete mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/InputPerson.java create mode 100644 extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/OutputPerson.java create mode 100644 extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollection.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollection.java new file mode 100644 index 0000000000000..26edbfe29cb43 --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollection.java @@ -0,0 +1,20 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +import java.util.ArrayList; +import java.util.List; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import io.quarkus.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.amazon.lambda.deployment.testing.model.OutputPerson; + +public abstract class AbstractInputCollectionOutputCollection implements RequestHandler, List> { + + @Override + public List handleRequest(List inputPeronList, Context context) { + List personList = new ArrayList<>(); + inputPeronList.forEach(person -> personList.add(new OutputPerson(person.getName()))); + return personList; + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImpl.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImpl.java new file mode 100644 index 0000000000000..6b8aff7356e9f --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImpl.java @@ -0,0 +1,5 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +public class AbstractInputCollectionOutputCollectionLambdaImpl extends AbstractInputCollectionOutputCollection { + +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImplTest.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImplTest.java new file mode 100644 index 0000000000000..ce5f9843936b5 --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/AbstractInputCollectionOutputCollectionLambdaImplTest.java @@ -0,0 +1,46 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.hasEntry; + +import java.util.ArrayList; +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.quarkus.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.amazon.lambda.deployment.testing.model.OutputPerson; +import io.quarkus.test.QuarkusUnitTest; + +public class AbstractInputCollectionOutputCollectionLambdaImplTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClasses(AbstractInputCollectionOutputCollectionLambdaImpl.class, AbstractInputCollectionOutputCollection.class, + InputPerson.class, OutputPerson.class)); + + @Test + void abstractRequestHandler_InputCollectionInputPerson_OutputCollectionOutputPerson() { + + List personList = new ArrayList<>(); + personList.add(new InputPerson("Chris")); + personList.add(new InputPerson("Fred")); + + given() + .body(personList) + .when() + .post() + .then() + .statusCode(200) + .body("", hasItem(hasEntry("outputname", "Chris"))) // OutputPerson serializes name with key outputname + .body("", hasItem(hasEntry("outputname", "Fred"))) + .body("", not(hasItem(hasEntry("name", "Chris")))) // make sure that there is no key name + .body("", not(hasItem(hasEntry("name", "Fred")))); + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java index f62a7ef7eef95..7b2ed22d57b92 100644 --- a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java @@ -3,10 +3,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -public class GreetingLambda implements RequestHandler { +import io.quarkus.amazon.lambda.deployment.testing.model.InputPerson; + +public class GreetingLambda implements RequestHandler { @Override - public String handleRequest(Person input, Context context) { + public String handleRequest(InputPerson input, Context context) { return "Hey " + input.getName(); } } diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambdaTest.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambdaTest.java new file mode 100644 index 0000000000000..899e2febbff0e --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambdaTest.java @@ -0,0 +1,38 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; + +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.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.test.QuarkusUnitTest; + +class GreetingLambdaTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClasses(GreetingLambda.class, InputPerson.class)); + + @Test + public void requestHandler_InputPerson_OutputString() throws Exception { + // you test your lambdas by invoking on http://localhost:8081 + // this works in dev mode too + + InputPerson in = new InputPerson("Stu"); + given() + .contentType("application/json") + .accept("application/json") + .body(in) + .when() + .post() + .then() + .statusCode(200) + .body(containsString("Hey Stu")); + } + +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java new file mode 100644 index 0000000000000..437cc4e6fcbc5 --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java @@ -0,0 +1,24 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +import java.util.ArrayList; +import java.util.List; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import io.quarkus.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.amazon.lambda.deployment.testing.model.OutputPerson; + +public class InputCollectionOutputCollectionLambda implements RequestHandler, List> { + + @Override + public List handleRequest(List people, Context context) { + + List outputPeople = new ArrayList<>(); + people.stream().parallel().forEach((person) -> { + outputPeople.add(new OutputPerson(person.getName())); + }); + + return outputPeople; + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambdaTest.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambdaTest.java new file mode 100644 index 0000000000000..907827ece866a --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambdaTest.java @@ -0,0 +1,45 @@ +package io.quarkus.amazon.lambda.deployment.testing; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.hasEntry; + +import java.util.ArrayList; +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.quarkus.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.amazon.lambda.deployment.testing.model.OutputPerson; +import io.quarkus.test.QuarkusUnitTest; + +public class InputCollectionOutputCollectionLambdaTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClasses(InputCollectionOutputCollectionLambda.class, InputPerson.class, OutputPerson.class)); + + @Test + void requestHandler_InputCollectionInputPerson_OutputCollectionOutputPerson() { + + List personList = new ArrayList<>(); + personList.add(new InputPerson("Chris")); + personList.add(new InputPerson("Fred")); + + given() + .body(personList) + .when() + .post() + .then() + .statusCode(200) + .body("", hasItem(hasEntry("outputname", "Chris"))) // OutputPerson serializes name with key outputname + .body("", hasItem(hasEntry("outputname", "Fred"))) + .body("", not(hasItem(hasEntry("name", "Chris")))) // make sure that there is no key name + .body("", not(hasItem(hasEntry("name", "Fred")))); + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java index cef9a2bda6761..60b108b89b094 100644 --- a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.amazon.lambda.deployment.testing.model.InputPerson; +import io.quarkus.amazon.lambda.deployment.testing.model.OutputPerson; import io.quarkus.test.ContinuousTestingTestUtils; import io.quarkus.test.QuarkusDevModeTest; @@ -21,7 +23,7 @@ public class LambdaDevServicesContinuousTestingTestCase { @Override public JavaArchive get() { return ShrinkWrap.create(JavaArchive.class) - .addClasses(GreetingLambda.class, Person.class) + .addClasses(GreetingLambda.class, InputPerson.class, OutputPerson.class) .addAsResource( new StringAsset(ContinuousTestingTestUtils.appProperties( "quarkus.log.category.\"io.quarkus.amazon.lambda.runtime\".level=DEBUG")), @@ -30,7 +32,7 @@ public JavaArchive get() { }).setTestArchiveProducer(new Supplier<>() { @Override public JavaArchive get() { - return ShrinkWrap.create(JavaArchive.class).addClass(LambdaHandlerET.class); + return ShrinkWrap.create(JavaArchive.class).addClass(GreetingLambdaTest.class); } }); @@ -45,7 +47,7 @@ public void testLambda() throws Exception { result = utils.waitForNextCompletion(); Assertions.assertEquals(0, result.getTotalTestsPassed()); Assertions.assertEquals(1, result.getTotalTestsFailed()); - test.modifyTestSourceFile(LambdaHandlerET.class, s -> s.replace("Hey", "Yo")); + test.modifyTestSourceFile(GreetingLambdaTest.class, s -> s.replace("Hey", "Yo")); result = utils.waitForNextCompletion(); Assertions.assertEquals(1, result.getTotalTestsPassed()); Assertions.assertEquals(0, result.getTotalTestsFailed()); diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java deleted file mode 100644 index 135b02bac0547..0000000000000 --- a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.quarkus.amazon.lambda.deployment.testing; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.containsString; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -class LambdaHandlerET { - - @Test - public void testSimpleLambdaSuccess() throws Exception { - // you test your lambdas by invoking on http://localhost:8081 - // this works in dev mode too - - Person in = new Person(); - in.setName("Stu"); - given() - .contentType("application/json") - .accept("application/json") - .body(in) - .when() - .post() - .then() - .statusCode(200) - .body(containsString("Hey Stu")); - } - -} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java deleted file mode 100644 index d2a4066a77dc2..0000000000000 --- a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.quarkus.amazon.lambda.deployment.testing; - -public class Person { - - private String name; - - public String getName() { - return name; - } - - public Person setName(String name) { - this.name = name; - return this; - } -} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/InputPerson.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/InputPerson.java new file mode 100644 index 0000000000000..b74c9482d8c3c --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/InputPerson.java @@ -0,0 +1,22 @@ +package io.quarkus.amazon.lambda.deployment.testing.model; + +public class InputPerson { + + public InputPerson() { + } + + public InputPerson(String name) { + this.name = name; + } + + private String name; + + public String getName() { + return name; + } + + public InputPerson setName(String name) { + this.name = name; + return this; + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/OutputPerson.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/OutputPerson.java new file mode 100644 index 0000000000000..f448321ffdd5f --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/model/OutputPerson.java @@ -0,0 +1,20 @@ +package io.quarkus.amazon.lambda.deployment.testing.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class OutputPerson { + + public OutputPerson() { + } + + public OutputPerson(String name) { + this.name = name; + } + + @JsonProperty("outputname") + private String name; + + public String getName() { + return name; + } +} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java index f8fb6d5b76568..49dc6d9d92a2b 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -16,6 +17,7 @@ import com.amazonaws.services.lambda.runtime.events.S3Event; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.amazon.lambda.runtime.handlers.CollectionInputReader; import io.quarkus.amazon.lambda.runtime.handlers.S3EventInputReader; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.LaunchMode; @@ -53,11 +55,15 @@ static void initializeHandlerClass(Class> handler ObjectMapper objectMapper = AmazonLambdaMapperRecorder.objectMapper; Method handlerMethod = discoverHandlerMethod(handlerClass); Class parameterType = handlerMethod.getParameterTypes()[0]; + if (parameterType.equals(S3Event.class)) { objectReader = new S3EventInputReader(objectMapper); + } else if (Collection.class.isAssignableFrom(parameterType)) { + objectReader = new CollectionInputReader<>(objectMapper, handlerMethod); } else { objectReader = new JacksonInputReader(objectMapper.readerFor(parameterType)); } + objectWriter = new JacksonOutputWriter(objectMapper.writerFor(handlerMethod.getReturnType())); } diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java new file mode 100644 index 0000000000000..a698bcdbf400f --- /dev/null +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java @@ -0,0 +1,28 @@ +package io.quarkus.amazon.lambda.runtime.handlers; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collection; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; + +import io.quarkus.amazon.lambda.runtime.LambdaInputReader; + +public class CollectionInputReader implements LambdaInputReader> { + final ObjectReader reader; + + public CollectionInputReader(ObjectMapper mapper, Method handler) { + Type genericParameterType = handler.getGenericParameterTypes()[0]; + JavaType constructParameterType = mapper.getTypeFactory().constructType(genericParameterType); + this.reader = mapper.readerFor(constructParameterType); + } + + @Override + public Collection readValue(InputStream is) throws IOException { + return this.reader.readValue(is); + } +} From 5458fb45550931563ae2362ca86cd703a27c4ccb Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 4 Jul 2024 13:49:40 +0200 Subject: [PATCH 02/16] Infinispan Extension adds SASL to service providers (cherry picked from commit e5a1efab98ab6b4764362b4ed106e551f2102afe) --- .../deployment/InfinispanClientProcessor.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index a52ff559827ff..47a397108eaa1 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -303,6 +303,22 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch "org.infinispan.client.hotrod.impl.consistenthash.SegmentConsistentHash") .build()); + // Elytron Classes + String[] elytronClasses = new String[] { + "org.wildfly.security.sasl.plain.PlainSaslClientFactory", + "org.wildfly.security.sasl.scram.ScramSaslClientFactory", + "org.wildfly.security.credential.BearerTokenCredential", + "org.wildfly.security.credential.GSSKerberosCredential", + "org.wildfly.security.credential.KeyPairCredential", + "org.wildfly.security.credential.PasswordCredential", + "org.wildfly.security.credential.PublicKeyCredential", + "org.wildfly.security.credential.SecretKeyCredential", + "org.wildfly.security.credential.SSHCredential", + "org.wildfly.security.credential.X509CertificateChainPrivateCredential", + "org.wildfly.security.credential.X509CertificateChainPublicCredential" + }; + + reflectiveClass.produce(ReflectiveClassBuildItem.builder(elytronClasses).build()); return new InfinispanPropertiesBuildItem(propertiesMap); } From ebed119d73cc7de3a7edfb0f847ae7e78b4f0de9 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 9 Jul 2024 10:23:08 -0500 Subject: [PATCH 03/16] Use `SecureDirectoryStream` to avoid FS problems and fix other minor issues in `IoUtils` Possible fix for #41767. (cherry picked from commit c58af08ae0ce59a469f3f23991977e31800f1415) --- .../io/quarkus/bootstrap/util/IoUtils.java | 154 ++++++++++++------ 1 file changed, 102 insertions(+), 52 deletions(-) diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/IoUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/IoUtils.java index 0c4006fc3dc74..18513222b7384 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/IoUtils.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/IoUtils.java @@ -1,21 +1,23 @@ package io.quarkus.bootstrap.util; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StringWriter; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SecureDirectoryStream; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; import java.util.Objects; @@ -29,8 +31,6 @@ */ public class IoUtils { - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - private static final Path TMP_DIR = Paths.get(PropertyUtils.getProperty("java.io.tmpdir")); private static final Logger log = Logger.getLogger(IoUtils.class); @@ -60,40 +60,36 @@ public static Path mkdirs(Path dir) { return dir; } + /** + * Recursively delete the file or directory given by {@code root}. + * The implementation will attempt to do so in a secure manner. + * Any problems encountered will be logged at {@code DEBUG} level. + * + * @param root the root path (must not be {@code null}) + */ public static void recursiveDelete(Path root) { - log.debugf("Recursively delete directory %s", root); + log.debugf("Recursively delete path %s", root); if (root == null || !Files.exists(root)) { return; } try { - Files.walkFileTree(root, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - try { - Files.delete(file); - } catch (IOException ex) { - log.debugf(ex, "Unable to delete file " + file); - } - return FileVisitResult.CONTINUE; + if (Files.isDirectory(root)) { + try (DirectoryStream ds = Files.newDirectoryStream(root)) { + recursiveDelete(ds); } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException e) - throws IOException { - if (e == null) { - try { - Files.delete(dir); - } catch (IOException ex) { - log.debugf(ex, "Unable to delete directory " + dir); - } - return FileVisitResult.CONTINUE; - } else { - // directory iteration failed - throw e; - } + try { + Files.delete(root); + } catch (IOException e) { + log.debugf(e, "Unable to delete directory %s", root); } - }); + } else { + log.debugf("Delete file %s", root); + try { + Files.delete(root); + } catch (IOException e) { + log.debugf(e, "Unable to delete file %s", root); + } + } } catch (IOException e) { log.debugf(e, "Error recursively deleting directory"); } @@ -101,9 +97,10 @@ public FileVisitResult postVisitDirectory(Path dir, IOException e) /** * Creates a new empty directory or empties an existing one. + * Any problems encountered while emptying the directory will be logged at {@code DEBUG} level. * * @param dir directory - * @throws IOException in case of a failure + * @throws IOException if creating or accessing the directory itself fails */ public static void createOrEmptyDir(Path dir) throws IOException { log.debugf("Create or empty directory %s", dir); @@ -113,17 +110,51 @@ public static void createOrEmptyDir(Path dir) throws IOException { Files.createDirectories(dir); return; } - if (!Files.isDirectory(dir)) { - throw new IllegalArgumentException(dir + " is not a directory"); + // recursively delete the *contents* of the directory, if any (keep the directory itself) + try (DirectoryStream ds = Files.newDirectoryStream(dir)) { + recursiveDelete(ds); + } + } + + private static void recursiveDelete(DirectoryStream ds) { + if (ds instanceof SecureDirectoryStream sds) { + // best, fastest, and most likely path for most OSes + recursiveDeleteSecure(sds); + } else { + // this may not work well on e.g. NFS, so we avoid this path if possible + for (Path p : ds) { + recursiveDelete(p); + } } - log.debugf("Iterate over contents of %s to delete its contents", dir); - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { - for (Path p : stream) { - if (Files.isDirectory(p)) { - recursiveDelete(p); - } else { - log.debugf("Delete file %s", p); - Files.delete(p); + } + + private static void recursiveDeleteSecure(SecureDirectoryStream sds) { + for (Path p : sds) { + Path file = p.getFileName(); + BasicFileAttributes attrs; + try { + attrs = sds.getFileAttributeView(file, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS) + .readAttributes(); + } catch (IOException e) { + log.debugf(e, "Unable to query file type of %s", p); + continue; + } + if (attrs.isDirectory()) { + try { + try (SecureDirectoryStream nested = sds.newDirectoryStream(file)) { + recursiveDeleteSecure(nested); + } + sds.deleteDirectory(file); + } catch (IOException e) { + log.debugf(e, "Unable to delete directory %s", p); + } + } else { + // log the whole path, not the file name + log.debugf("Delete file %s", p); + try { + sds.deleteFile(file); + } catch (IOException e) { + log.debugf(e, "Unable to delete file %s", p); } } } @@ -163,24 +194,43 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) return target; } + /** + * Read the contents of a file as a string. + * + * @param file the file to read (must not be {@code null}) + * @return the file content, as a string (not {@code null}) + * @throws IOException if an error occurs when reading the file + * @deprecated Use {@link Files#readString(Path, Charset)} instead. + */ + @Deprecated(forRemoval = true) public static String readFile(Path file) throws IOException { - final char[] charBuffer = new char[DEFAULT_BUFFER_SIZE]; - int n = 0; - final StringWriter output = new StringWriter(); - try (BufferedReader input = Files.newBufferedReader(file)) { - while ((n = input.read(charBuffer)) != -1) { - output.write(charBuffer, 0, n); - } - } - return output.getBuffer().toString(); + return Files.readString(file, StandardCharsets.UTF_8); } + /** + * Copy the input stream to the given output stream. + * Calling this method is identical to calling {@code in.transferTo(out)}. + * + * @param out the output stream (must not be {@code null}) + * @param in the input stream (must not be {@code null}) + * @throws IOException if an error occurs during the copy + * @see InputStream#transferTo(OutputStream) + */ public static void copy(OutputStream out, InputStream in) throws IOException { in.transferTo(out); } + /** + * Write a string to a file using UTF-8 encoding. + * The file will be created if it does not exist, and truncated if it is not empty. + * + * @param file the file to write (must not be {@code null}) + * @param content the string to write to the file (must not be {@code null}) + * @throws IOException if an error occurs when writing the file + */ public static void writeFile(Path file, String content) throws IOException { - Files.write(file, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + Files.writeString(file, content, StandardCharsets.UTF_8, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); } } From 3b3dc13c2e5ddc3a24d6826701215e918d885252 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 9 Jul 2024 11:30:59 +0200 Subject: [PATCH 04/16] Scheduler: fix Trigger#getNextFireTime() for cron-based jobs - fixes #41717 (cherry picked from commit 30af1a6bffe303d275c6f03acb958109c5436bd2) --- .../ScheduledMethodTimeZoneTest.java | 2 +- .../timezone/TriggerNextFireTimeZoneTest.java | 74 +++++++++++++++ .../timezone/TriggerPrevFireTimeZoneTest.java | 93 +++++++++++++++++++ .../quarkus/scheduler/ScheduledExecution.java | 9 ++ .../java/io/quarkus/scheduler/Trigger.java | 12 +++ .../ScheduledMethodTimeZoneTest.java | 3 +- .../timezone/TriggerNextFireTimeZoneTest.java | 74 +++++++++++++++ .../timezone/TriggerPrevFireTimeZoneTest.java | 93 +++++++++++++++++++ .../scheduler/runtime/SimpleScheduler.java | 23 +++-- 9 files changed, 371 insertions(+), 12 deletions(-) rename extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/{ => timezone}/ScheduledMethodTimeZoneTest.java (98%) create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerNextFireTimeZoneTest.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerPrevFireTimeZoneTest.java rename extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/{ => timezone}/ScheduledMethodTimeZoneTest.java (97%) create mode 100644 extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerNextFireTimeZoneTest.java create mode 100644 extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerPrevFireTimeZoneTest.java diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ScheduledMethodTimeZoneTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/ScheduledMethodTimeZoneTest.java similarity index 98% rename from extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ScheduledMethodTimeZoneTest.java rename to extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/ScheduledMethodTimeZoneTest.java index 95d4d47724c53..38b42d218c2a6 100644 --- a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ScheduledMethodTimeZoneTest.java +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/ScheduledMethodTimeZoneTest.java @@ -1,4 +1,4 @@ -package io.quarkus.quartz.test; +package io.quarkus.quartz.test.timezone; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerNextFireTimeZoneTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerNextFireTimeZoneTest.java new file mode 100644 index 0000000000000..4e588223254a3 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerNextFireTimeZoneTest.java @@ -0,0 +1,74 @@ +package io.quarkus.quartz.test.timezone; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.ScheduledExecution; +import io.quarkus.scheduler.Scheduler; +import io.quarkus.scheduler.Trigger; +import io.quarkus.test.QuarkusUnitTest; + +public class TriggerNextFireTimeZoneTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot(root -> { + root.addClasses(Jobs.class); + }); + + @Inject + Scheduler scheduler; + + @Test + public void testScheduledJobs() throws InterruptedException { + Trigger prague = scheduler.getScheduledJob("prague"); + Trigger boston = scheduler.getScheduledJob("boston"); + Trigger ulaanbaatar = scheduler.getScheduledJob("ulaanbaatar"); + assertNotNull(prague); + assertNotNull(boston); + assertNotNull(ulaanbaatar); + Instant pragueNext = prague.getNextFireTime(); + Instant bostonNext = boston.getNextFireTime(); + Instant ulaanbaatarNext = ulaanbaatar.getNextFireTime(); + assertTime(pragueNext.atZone(ZoneId.of("Europe/Prague"))); + assertTime(bostonNext.atZone(ZoneId.of("America/New_York"))); + assertTime(ulaanbaatarNext.atZone(ZoneId.of("Asia/Ulaanbaatar"))); + } + + private static void assertTime(ZonedDateTime time) { + assertEquals(20, time.getHour()); + assertEquals(30, time.getMinute()); + assertEquals(0, time.getSecond()); + } + + static class Jobs { + + @Scheduled(identity = "prague", cron = "0 30 20 * * ?", timeZone = "Europe/Prague") + void withPragueTimezone(ScheduledExecution execution) { + assertNotEquals(execution.getFireTime(), execution.getScheduledFireTime()); + assertTime(execution.getScheduledFireTime().atZone(ZoneId.of("Europe/Prague"))); + } + + @Scheduled(identity = "boston", cron = "0 30 20 * * ?", timeZone = "America/New_York") + void withBostonTimezone() { + } + + @Scheduled(identity = "ulaanbaatar", cron = "0 30 20 * * ?", timeZone = "Asia/Ulaanbaatar") + void withIstanbulTimezone(ScheduledExecution execution) { + assertTime(execution.getScheduledFireTime().atZone(ZoneId.of("Asia/Ulaanbaatar"))); + } + + } + +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerPrevFireTimeZoneTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerPrevFireTimeZoneTest.java new file mode 100644 index 0000000000000..4da6a548d7e43 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/timezone/TriggerPrevFireTimeZoneTest.java @@ -0,0 +1,93 @@ +package io.quarkus.quartz.test.timezone; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; +import io.quarkus.scheduler.Trigger; +import io.quarkus.test.QuarkusUnitTest; + +public class TriggerPrevFireTimeZoneTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot(root -> { + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime prague = now.withZoneSameInstant(ZoneId.of("Europe/Prague")); + ZonedDateTime istanbul = now.withZoneSameInstant(ZoneId.of("Europe/Istanbul")); + // For example, the current date-time is 2024-07-09 10:08:00; + // the default time zone is Europe/London + // then the config should look like: + // simpleJobs1.cron=0/1 * 11 * * ? + // simpleJobs2.cron=0/1 * 12 * * ? + String properties = String.format( + "simpleJobs1.cron=0/1 * %s * * ?\n" + + "simpleJobs1.hour=%s\n" + + "simpleJobs2.cron=0/1 * %s * * ?\n" + + "simpleJobs2.hour=%s", + prague.getHour(), prague.getHour(), istanbul.getHour(), istanbul.getHour()); + root.addClasses(Jobs.class) + .addAsResource( + new StringAsset(properties), + "application.properties"); + }); + + @ConfigProperty(name = "simpleJobs1.hour") + int pragueHour; + + @ConfigProperty(name = "simpleJobs2.hour") + int istanbulHour; + + @Inject + Scheduler scheduler; + + @Test + public void testScheduledJobs() throws InterruptedException { + assertTrue(Jobs.PRAGUE_LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.ISTANBUL_LATCH.await(5, TimeUnit.SECONDS)); + Trigger prague = scheduler.getScheduledJob("prague"); + Trigger istanbul = scheduler.getScheduledJob("istanbul"); + assertNotNull(prague); + assertNotNull(istanbul); + Instant praguePrev = prague.getPreviousFireTime(); + Instant istanbulPrev = istanbul.getPreviousFireTime(); + assertNotNull(praguePrev); + assertNotNull(istanbulPrev); + assertEquals(praguePrev, istanbulPrev); + assertEquals(pragueHour, praguePrev.atZone(ZoneId.of("Europe/Prague")).getHour()); + assertEquals(istanbulHour, istanbulPrev.atZone(ZoneId.of("Europe/Istanbul")).getHour()); + } + + static class Jobs { + + static final CountDownLatch PRAGUE_LATCH = new CountDownLatch(1); + static final CountDownLatch ISTANBUL_LATCH = new CountDownLatch(1); + + @Scheduled(identity = "prague", cron = "{simpleJobs1.cron}", timeZone = "Europe/Prague") + void withPragueTimezone() { + PRAGUE_LATCH.countDown(); + } + + @Scheduled(identity = "istanbul", cron = "{simpleJobs2.cron}", timeZone = "Europe/Istanbul") + void withIstanbulTimezone() { + ISTANBUL_LATCH.countDown(); + } + + } + +} diff --git a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/ScheduledExecution.java b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/ScheduledExecution.java index 5f69240af667c..fda1ac26fec88 100644 --- a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/ScheduledExecution.java +++ b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/ScheduledExecution.java @@ -14,6 +14,9 @@ public interface ScheduledExecution { Trigger getTrigger(); /** + * The returned {@code Instant} is converted from the date-time in the default timezone. A timezone of a cron-based job + * is not taken into account. + *

* Unlike {@link Trigger#getPreviousFireTime()} this method always returns the same value. * * @return the time the associated trigger was fired @@ -21,6 +24,12 @@ public interface ScheduledExecution { Instant getFireTime(); /** + * If the trigger represents a cron-based job with a timezone, then the returned {@code Instant} takes the timezone into + * account. + *

+ * For example, if there is a job with cron expression {@code 0 30 20 ? * * *} with timezone {@code Europe/Berlin}, + * then the return value looks like {@code 2024-07-08T18:30:00Z}. And {@link Instant#atZone(java.time.ZoneId)} for + * {@code Europe/Berlin} would yield {@code 2024-07-08T20:30+02:00[Europe/Berlin]}. * * @return the time the action was scheduled for */ diff --git a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Trigger.java b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Trigger.java index c076e5712bc0e..0a5f94d48ffb3 100644 --- a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Trigger.java +++ b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Trigger.java @@ -21,12 +21,24 @@ public interface Trigger { String getId(); /** + * If the trigger represents a cron-based job with a timezone, then the returned {@code Instant} takes the timezone into + * account. + *

+ * For example, if there is a job with cron expression {@code 0 30 20 ? * * *} with timezone {@code Europe/Berlin}, then the + * return value looks like {@code 2024-07-08T18:30:00Z}. And {@link Instant#atZone(java.time.ZoneId)} for + * {@code Europe/Berlin} would yield {@code 2024-07-08T20:30+02:00[Europe/Berlin]}. * * @return the next time at which the trigger is scheduled to fire, or {@code null} if it will not fire again */ Instant getNextFireTime(); /** + * If the trigger represents a cron-based job with a timezone, then the returned {@code Instant} takes the timezone into + * account. + *

+ * For example, if there is a job with cron expression {@code 0 30 20 ? * * *} with timezone {@code Europe/Berlin}, then the + * return value looks like {@code 2024-07-08T18:30:00Z}. And {@link Instant#atZone(java.time.ZoneId)} for + * {@code Europe/Berlin} would yield {@code 2024-07-08T20:30+02:00[Europe/Berlin]}. * * @return the previous time at which the trigger fired, or {@code null} if it has not fired yet */ diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ScheduledMethodTimeZoneTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/ScheduledMethodTimeZoneTest.java similarity index 97% rename from extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ScheduledMethodTimeZoneTest.java rename to extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/ScheduledMethodTimeZoneTest.java index aba645216812a..9db547b46e7a0 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ScheduledMethodTimeZoneTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/ScheduledMethodTimeZoneTest.java @@ -1,4 +1,4 @@ -package io.quarkus.scheduler.test; +package io.quarkus.scheduler.test.timezone; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -41,7 +41,6 @@ public class ScheduledMethodTimeZoneTest { + "simpleJobs2.cron=0/1 * %s * * ?\n" + "simpleJobs2.timeZone=%s", now.getHour(), timeZone, job2Hour, timeZone); - // System.out.println(properties); jar.addClasses(Jobs.class) .addAsResource( new StringAsset(properties), diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerNextFireTimeZoneTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerNextFireTimeZoneTest.java new file mode 100644 index 0000000000000..d301a0e1cff5d --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerNextFireTimeZoneTest.java @@ -0,0 +1,74 @@ +package io.quarkus.scheduler.test.timezone; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.ScheduledExecution; +import io.quarkus.scheduler.Scheduler; +import io.quarkus.scheduler.Trigger; +import io.quarkus.test.QuarkusUnitTest; + +public class TriggerNextFireTimeZoneTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot(root -> { + root.addClasses(Jobs.class); + }); + + @Inject + Scheduler scheduler; + + @Test + public void testScheduledJobs() throws InterruptedException { + Trigger prague = scheduler.getScheduledJob("prague"); + Trigger boston = scheduler.getScheduledJob("boston"); + Trigger ulaanbaatar = scheduler.getScheduledJob("ulaanbaatar"); + assertNotNull(prague); + assertNotNull(boston); + assertNotNull(ulaanbaatar); + Instant pragueNext = prague.getNextFireTime(); + Instant bostonNext = boston.getNextFireTime(); + Instant ulaanbaatarNext = ulaanbaatar.getNextFireTime(); + assertTime(pragueNext.atZone(ZoneId.of("Europe/Prague"))); + assertTime(bostonNext.atZone(ZoneId.of("America/New_York"))); + assertTime(ulaanbaatarNext.atZone(ZoneId.of("Asia/Ulaanbaatar"))); + } + + private static void assertTime(ZonedDateTime time) { + assertEquals(20, time.getHour()); + assertEquals(30, time.getMinute()); + assertEquals(0, time.getSecond()); + } + + static class Jobs { + + @Scheduled(identity = "prague", cron = "0 30 20 * * ?", timeZone = "Europe/Prague") + void withPragueTimezone(ScheduledExecution execution) { + assertNotEquals(execution.getFireTime(), execution.getScheduledFireTime()); + assertTime(execution.getScheduledFireTime().atZone(ZoneId.of("Europe/Prague"))); + } + + @Scheduled(identity = "boston", cron = "0 30 20 * * ?", timeZone = "America/New_York") + void withBostonTimezone() { + } + + @Scheduled(identity = "ulaanbaatar", cron = "0 30 20 * * ?", timeZone = "Asia/Ulaanbaatar") + void withIstanbulTimezone(ScheduledExecution execution) { + assertTime(execution.getScheduledFireTime().atZone(ZoneId.of("Asia/Ulaanbaatar"))); + } + + } + +} diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerPrevFireTimeZoneTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerPrevFireTimeZoneTest.java new file mode 100644 index 0000000000000..ed1ef873b77b4 --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/timezone/TriggerPrevFireTimeZoneTest.java @@ -0,0 +1,93 @@ +package io.quarkus.scheduler.test.timezone; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; +import io.quarkus.scheduler.Trigger; +import io.quarkus.test.QuarkusUnitTest; + +public class TriggerPrevFireTimeZoneTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot(root -> { + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime prague = now.withZoneSameInstant(ZoneId.of("Europe/Prague")); + ZonedDateTime istanbul = now.withZoneSameInstant(ZoneId.of("Europe/Istanbul")); + // For example, the current date-time is 2024-07-09 10:08:00; + // the default time zone is Europe/London + // then the config should look like: + // simpleJobs1.cron=0/1 * 11 * * ? + // simpleJobs2.cron=0/1 * 12 * * ? + String properties = String.format( + "simpleJobs1.cron=0/1 * %s * * ?\n" + + "simpleJobs1.hour=%s\n" + + "simpleJobs2.cron=0/1 * %s * * ?\n" + + "simpleJobs2.hour=%s", + prague.getHour(), prague.getHour(), istanbul.getHour(), istanbul.getHour()); + root.addClasses(Jobs.class) + .addAsResource( + new StringAsset(properties), + "application.properties"); + }); + + @ConfigProperty(name = "simpleJobs1.hour") + int pragueHour; + + @ConfigProperty(name = "simpleJobs2.hour") + int istanbulHour; + + @Inject + Scheduler scheduler; + + @Test + public void testScheduledJobs() throws InterruptedException { + assertTrue(Jobs.PRAGUE_LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.ISTANBUL_LATCH.await(5, TimeUnit.SECONDS)); + Trigger prague = scheduler.getScheduledJob("prague"); + Trigger istanbul = scheduler.getScheduledJob("istanbul"); + assertNotNull(prague); + assertNotNull(istanbul); + Instant praguePrev = prague.getPreviousFireTime(); + Instant istanbulPrev = istanbul.getPreviousFireTime(); + assertNotNull(praguePrev); + assertNotNull(istanbulPrev); + assertEquals(praguePrev, istanbulPrev); + assertEquals(pragueHour, praguePrev.atZone(ZoneId.of("Europe/Prague")).getHour()); + assertEquals(istanbulHour, istanbulPrev.atZone(ZoneId.of("Europe/Istanbul")).getHour()); + } + + static class Jobs { + + static final CountDownLatch PRAGUE_LATCH = new CountDownLatch(1); + static final CountDownLatch ISTANBUL_LATCH = new CountDownLatch(1); + + @Scheduled(identity = "prague", cron = "{simpleJobs1.cron}", timeZone = "Europe/Prague") + void withPragueTimezone() { + PRAGUE_LATCH.countDown(); + } + + @Scheduled(identity = "istanbul", cron = "{simpleJobs2.cron}", timeZone = "Europe/Istanbul") + void withIstanbulTimezone() { + ISTANBUL_LATCH.countDown(); + } + + } + +} diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java index ce6d7482341da..687c081bbfe50 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java @@ -589,28 +589,29 @@ static class CronTrigger extends SimpleTrigger { super(id, start, description); this.cron = cron; this.executionTime = ExecutionTime.forCron(cron); - this.lastFireTime = start; this.gracePeriod = gracePeriod; this.timeZone = timeZone; + // The last fire time stores the zoned time + this.lastFireTime = zoned(start); } @Override public Instant getNextFireTime() { - Optional nextFireTime = executionTime.nextExecution(lastFireTime); - return nextFireTime.isPresent() ? nextFireTime.get().toInstant() : null; + return executionTime.nextExecution(lastFireTime).map(ZonedDateTime::toInstant).orElse(null); } + @Override ZonedDateTime evaluate(ZonedDateTime now) { if (now.isBefore(start)) { return null; } - ZonedDateTime zonedNow = timeZone == null ? now : now.withZoneSameInstant(timeZone); - Optional lastExecution = executionTime.lastExecution(zonedNow); + now = zoned(now); + Optional lastExecution = executionTime.lastExecution(now); if (lastExecution.isPresent()) { ZonedDateTime lastTruncated = lastExecution.get().truncatedTo(ChronoUnit.SECONDS); - if (zonedNow.isAfter(lastTruncated) && lastFireTime.isBefore(lastTruncated)) { + if (now.isAfter(lastTruncated) && lastFireTime.isBefore(lastTruncated)) { LOG.tracef("%s fired, last=%s", this, lastTruncated); - lastFireTime = zonedNow; + lastFireTime = now; return lastTruncated; } } @@ -623,9 +624,9 @@ public boolean isOverdue() { if (now.isBefore(start)) { return false; } - ZonedDateTime zonedNow = timeZone == null ? now : now.withZoneSameInstant(timeZone); + now = zoned(now); Optional nextFireTime = executionTime.nextExecution(lastFireTime); - return nextFireTime.isEmpty() || nextFireTime.get().plus(gracePeriod).isBefore(zonedNow); + return nextFireTime.isEmpty() || nextFireTime.get().plus(gracePeriod).isBefore(now); } @Override @@ -634,6 +635,10 @@ public String toString() { + timeZone + "]"; } + private ZonedDateTime zoned(ZonedDateTime time) { + return timeZone == null ? time : time.withZoneSameInstant(timeZone); + } + } static class SimpleScheduledExecution implements ScheduledExecution { From 277cc25472ac43aa54f3d54924a0030ba6c4ba3e Mon Sep 17 00:00:00 2001 From: Jakub Jedlicka Date: Wed, 10 Jul 2024 16:23:21 +0200 Subject: [PATCH 05/16] Fix code example for JSON serialisation in rest guide (cherry picked from commit 656b38be0b014f469ef232d34047211b3c5383f1) --- docs/src/main/asciidoc/rest.adoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/rest.adoc b/docs/src/main/asciidoc/rest.adoc index 5531dbb5c0001..f40b865e34917 100644 --- a/docs/src/main/asciidoc/rest.adoc +++ b/docs/src/main/asciidoc/rest.adoc @@ -1420,10 +1420,11 @@ public class Person { @SecureField(rolesAllowed = "${role:admin}") <1> private String address; - public Person(Long id, String first, String last) { + public Person(Long id, String first, String last, String address) { this.id = id; this.first = first; this.last = last; + this.address = address; } public Long getId() { @@ -1466,7 +1467,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Response; @Path("person") -public class Person { +public class PersonResource { @Path("{id}") @GET From 1f57bd17c5b26e8b32364be3b083c9b520818919 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 11 Jul 2024 09:44:37 +0300 Subject: [PATCH 06/16] Properly close AsyncFile in Quarkus REST Fixes: #41811 (cherry picked from commit a010cbd7d29061d9e53b2121556461c8203245aa) --- .../serializers/ServerMutinyAsyncFileMessageBodyWriter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/serializers/ServerMutinyAsyncFileMessageBodyWriter.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/serializers/ServerMutinyAsyncFileMessageBodyWriter.java index 2262c937af40e..ec06ca2cc35bd 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/serializers/ServerMutinyAsyncFileMessageBodyWriter.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/serializers/ServerMutinyAsyncFileMessageBodyWriter.java @@ -65,7 +65,9 @@ public void writeResponse(AsyncFile file, Type genericType, ServerRequestContext file.endHandler(new Runnable() { @Override public void run() { - file.close(); + // we don't need to wait for the file to be closed, we just need to make sure it does get closed + //noinspection ResultOfMethodCallIgnored + file.close().subscribeAsCompletionStage(); response.end(); // Not sure if I need to resume, actually ctx.resume(); From 24fa941720ce08757fe3da955d471373ad1e8ef0 Mon Sep 17 00:00:00 2001 From: xstefank Date: Wed, 10 Jul 2024 16:48:35 +0200 Subject: [PATCH 07/16] Create new vertx context for blocking health checks (cherry picked from commit d5d0aeec7ae444978017e53206aaf6011015ac04) --- ...kingChecksVertxContextDuplicationTest.java | 65 +++++++++++++++++++ .../QuarkusAsyncHealthCheckFactory.java | 19 +++++- .../runtime/SmallRyeHealthHandlerBase.java | 2 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingChecksVertxContextDuplicationTest.java diff --git a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingChecksVertxContextDuplicationTest.java b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingChecksVertxContextDuplicationTest.java new file mode 100644 index 0000000000000..6fd84159bd3f4 --- /dev/null +++ b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingChecksVertxContextDuplicationTest.java @@ -0,0 +1,65 @@ +package io.quarkus.smallrye.health.test; + +import static org.hamcrest.Matchers.is; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Liveness; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Assertions; +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.parsing.Parser; +import io.vertx.core.Context; +import io.vertx.core.Vertx; + +class BlockingChecksVertxContextDuplicationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ContextCaptureCheck1.class, ContextCaptureCheck2.class) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @Test + void testBlockingChecksPropagateVertxContext() { + try { + RestAssured.defaultParser = Parser.JSON; + RestAssured.when().get("/q/health").then() + .body("status", is("UP"), + "checks.size()", is(2)); + + Assertions.assertNotEquals(ContextCaptureCheck1.capturedContext, ContextCaptureCheck2.capturedContext, + "Expected different contexts to be propagated into different blocking health checks"); + } finally { + RestAssured.reset(); + } + } + + @Liveness + public static class ContextCaptureCheck1 implements HealthCheck { + + public static Context capturedContext = null; + + @Override + public HealthCheckResponse call() { + capturedContext = Vertx.currentContext(); + return HealthCheckResponse.up("ContextCaptureCheck1"); + } + } + + @Liveness + public static class ContextCaptureCheck2 implements HealthCheck { + + public static Context capturedContext = null; + + @Override + public HealthCheckResponse call() { + capturedContext = Vertx.currentContext(); + return HealthCheckResponse.up("ContextCaptureCheck2"); + } + } +} diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java index cfec52c933b1b..bd7c236dce2e2 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java @@ -1,14 +1,19 @@ package io.quarkus.smallrye.health.runtime; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; + import jakarta.inject.Singleton; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; +import io.smallrye.common.vertx.VertxContext; import io.smallrye.health.AsyncHealthCheckFactory; import io.smallrye.health.api.AsyncHealthCheck; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.vertx.MutinyHelper; +import io.vertx.core.Context; import io.vertx.core.Vertx; /** @@ -27,7 +32,19 @@ public QuarkusAsyncHealthCheckFactory(Vertx vertx) { @Override public Uni callSync(HealthCheck healthCheck) { Uni healthCheckResponseUni = super.callSync(healthCheck); - return healthCheckResponseUni.runSubscriptionOn(MutinyHelper.blockingExecutor(vertx, false)); + return healthCheckResponseUni.runSubscriptionOn(new Executor() { + @Override + public void execute(Runnable command) { + Context duplicatedContext = VertxContext.createNewDuplicatedContext(vertx.getOrCreateContext()); + duplicatedContext.executeBlocking(new Callable() { + @Override + public Void call() throws Exception { + command.run(); + return null; + } + }, false); + } + }); } @Override diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java index cc0bb85cce758..c35577a1d37d7 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java @@ -63,7 +63,7 @@ private void doHandle(RoutingContext ctx, ManagedContext requestContext) { .set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8") .set(HttpHeaders.CACHE_CONTROL, "no-store"); Buffer buffer = Buffer.buffer(256); // this size seems to cover the basic health checks - try (BufferOutputStream outputStream = new BufferOutputStream(buffer);) { + try (BufferOutputStream outputStream = new BufferOutputStream(buffer)) { reporter.reportHealth(outputStream, health); resp.end(buffer); } catch (IOException e) { From 29fc5a5a3703528e272724d6085649d11ccaf978 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 1 Jul 2024 19:30:46 +0200 Subject: [PATCH 08/16] Update quarkus-build-caching-extension to 1.3 Should fix the failing tests due to the cache not including quarkus-artifact.properties. We will need to merge this update in all supported branches and then clear the cache. (cherry picked from commit b6c96b5cd8b854989c7aa0b7c0beb8fe5ecdc265) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 7aaec6dcc712a..d11d056ef6041 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -12,7 +12,7 @@ com.gradle quarkus-build-caching-extension - 1.2 + 1.3 io.quarkus.develocity From d86eaee8a1f9e726d117575cfb35cfc5e82293ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:55:42 +0000 Subject: [PATCH 09/16] Bump com.gradle:quarkus-build-caching-extension from 1.3 to 1.4 Bumps [com.gradle:quarkus-build-caching-extension](https://github.com/gradle/develocity-build-config-samples) from 1.3 to 1.4. - [Release notes](https://github.com/gradle/develocity-build-config-samples/releases) - [Commits](https://github.com/gradle/develocity-build-config-samples/compare/v1.3...v1.4) --- updated-dependencies: - dependency-name: com.gradle:quarkus-build-caching-extension dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit 558697974b986abe73afa7b4633c1b6df65895ce) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index d11d056ef6041..6e53c44dcf554 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -12,7 +12,7 @@ com.gradle quarkus-build-caching-extension - 1.3 + 1.4 io.quarkus.develocity From 471e5b15651246c3df9b6c540af1ab0e44c72088 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 12 Jul 2024 11:26:16 +0200 Subject: [PATCH 10/16] WebSockets Next: broadcasting fixes - intentionally ignore 'WebSocket is closed' failures - do not fail fast but collect all failures (cherry picked from commit cf67cc1e6a89af4e92fa32bfe1fb73dd23c66b82) --- .../websockets/next/runtime/Endpoints.java | 2 +- .../next/runtime/WebSocketConnectionImpl.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java index 26bf46d6421a8..12a2b327fa6b1 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java @@ -291,7 +291,7 @@ private static boolean isSecurityFailure(Throwable throwable) { || throwable instanceof ForbiddenException; } - private static boolean isWebSocketIsClosedFailure(Throwable throwable, WebSocketConnectionBase connection) { + static boolean isWebSocketIsClosedFailure(Throwable throwable, WebSocketConnectionBase connection) { if (!connection.isClosed()) { return false; } diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectionImpl.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectionImpl.java index d1d4cad07638e..de23dd4779d78 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectionImpl.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectionImpl.java @@ -219,18 +219,26 @@ public Uni sendPong(Buffer data) { throw new UnsupportedOperationException(); } - private Uni doSend(BiFunction> function, M message) { + private Uni doSend(BiFunction> sendFunction, M message) { Set connections = connectionManager.getConnections(generatedEndpointClass); if (connections.isEmpty()) { return Uni.createFrom().voidItem(); } List> unis = new ArrayList<>(connections.size()); for (WebSocketConnection connection : connections) { - if (connection.isOpen() && (filter == null || filter.test(connection))) { - unis.add(function.apply(connection, message)); + if (connection.isOpen() + && (filter == null || filter.test(connection))) { + unis.add(sendFunction.apply(connection, message) + // Intentionally ignore 'WebSocket is closed' failures + // It might happen that the connection is closed in the mean time + .onFailure(t -> Endpoints.isWebSocketIsClosedFailure(t, (WebSocketConnectionBase) connection)) + .recoverWithNull()); } } - return unis.isEmpty() ? Uni.createFrom().voidItem() : Uni.join().all(unis).andFailFast().replaceWithVoid(); + if (unis.isEmpty()) { + return Uni.createFrom().voidItem(); + } + return Uni.join().all(unis).andCollectFailures().replaceWithVoid(); } } From 8a711d4364e25864ff54b381fb994c45c5fb4381 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 11 Jul 2024 13:42:57 -0300 Subject: [PATCH 11/16] Module `java.security.jgss` should export `sun.security.jgss` (cherry picked from commit ab2bd29b466fe9061cdc8fc03da1b65db6a62e15) --- .../io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 1c1d8f54b73e1..54c11d2cc2921 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -788,6 +788,7 @@ public NativeImageInvokerInfo build() { * control its actual inclusion which will depend on the usual analysis. */ nativeImageArgs.add("-J--add-exports=java.security.jgss/sun.security.krb5=ALL-UNNAMED"); + nativeImageArgs.add("-J--add-exports=java.security.jgss/sun.security.jgss=ALL-UNNAMED"); //address https://github.com/quarkusio/quarkus-quickstarts/issues/993 nativeImageArgs.add("-J--add-opens=java.base/java.text=ALL-UNNAMED"); From d915819243d8a98ad8331479f561445d99456d29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:01:06 +0000 Subject: [PATCH 12/16] Bump com.gradle:quarkus-build-caching-extension from 1.4 to 1.5 Bumps [com.gradle:quarkus-build-caching-extension](https://github.com/gradle/develocity-build-config-samples) from 1.4 to 1.5. - [Release notes](https://github.com/gradle/develocity-build-config-samples/releases) - [Commits](https://github.com/gradle/develocity-build-config-samples/compare/v1.4...v1.5) --- updated-dependencies: - dependency-name: com.gradle:quarkus-build-caching-extension dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit 1ed1b2f4be74141016ab25f9447d17ce61a200ee) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 6e53c44dcf554..da9c47c0517fa 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -12,7 +12,7 @@ com.gradle quarkus-build-caching-extension - 1.4 + 1.5 io.quarkus.develocity From 2e0ffaa41820bf828427fadd6d72669772a60d9c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 Jul 2024 16:21:36 +0300 Subject: [PATCH 13/16] Fix flaky InputCollectionOutputCollectionLambdaTest (cherry picked from commit 852e3868b25b4e6514d633f5dcb25318f2287fc4) --- .../testing/InputCollectionOutputCollectionLambda.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java index 437cc4e6fcbc5..468cd6f255a6a 100644 --- a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/InputCollectionOutputCollectionLambda.java @@ -15,7 +15,7 @@ public class InputCollectionOutputCollectionLambda implements RequestHandler handleRequest(List people, Context context) { List outputPeople = new ArrayList<>(); - people.stream().parallel().forEach((person) -> { + people.forEach((person) -> { outputPeople.add(new OutputPerson(person.getName())); }); From 0331a04d1cf90d7d712977f386015180869d97fb Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Mon, 15 Jul 2024 18:10:36 +0200 Subject: [PATCH 14/16] Use quarkusConditional prefix for Quarkus conditional dependency configurations (cherry picked from commit 647a3d5f9c1e10a1ff90c6dc1370a2802a25e050) --- .../ConditionalDependenciesEnabler.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index c090d3b1701a7..4c0bc2fdee5ab 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -170,8 +170,7 @@ private void queueConditionalDependency(ExtensionDependency extension, Depend private Configuration createConditionalDependenciesConfiguration(Project project, Dependency conditionalDep) { // previously we used a detached configuration here but apparently extendsFrom(enforcedPlatforms) // wouldn't actually enforce platforms on a detached configuration - final String name = conditionalDep.getGroup() + ":" + conditionalDep.getName() + ":" + conditionalDep.getVersion() - + "Configuration"; + var name = getConditionalConfigurationName(conditionalDep); var config = project.getConfigurations().findByName(name); if (config == null) { project.getConfigurations().register(name, configuration -> { @@ -184,6 +183,31 @@ private Configuration createConditionalDependenciesConfiguration(Project project return config; } + private static String getConditionalConfigurationName(Dependency conditionalDep) { + var name = new StringBuilder().append("quarkusConditional"); + appendCapitalized(name, conditionalDep.getGroup()); + appendCapitalized(name, conditionalDep.getName()); + appendCapitalized(name, conditionalDep.getVersion()); + return name.append("Configuration").toString(); + } + + private static void appendCapitalized(StringBuilder sb, String part) { + if (part != null && !part.isEmpty()) { + boolean toUpperCase = true; + for (int i = 0; i < part.length(); ++i) { + var c = part.charAt(i); + if (toUpperCase) { + sb.append(Character.toUpperCase(c)); + toUpperCase = false; + } else if (c == '.' || c == '-') { + toUpperCase = true; + } else { + sb.append(c); + } + } + } + } + private void enableConditionalDependency(ModuleVersionIdentifier dependency) { final Set> extensions = featureVariants.remove(getFeatureKey(dependency)); if (extensions == null) { From d82e65284cd23d5a9767437d9ca5fe62299162ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:43:29 +0000 Subject: [PATCH 15/16] Bump com.gradle:quarkus-build-caching-extension from 1.5 to 1.6 Bumps [com.gradle:quarkus-build-caching-extension](https://github.com/gradle/develocity-build-config-samples) from 1.5 to 1.6. - [Release notes](https://github.com/gradle/develocity-build-config-samples/releases) - [Commits](https://github.com/gradle/develocity-build-config-samples/compare/v1.5...v1.6) --- updated-dependencies: - dependency-name: com.gradle:quarkus-build-caching-extension dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit b8bc206677baf8183e41d580cd58e564582cccdb) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index da9c47c0517fa..2be0032ec7c0a 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -12,7 +12,7 @@ com.gradle quarkus-build-caching-extension - 1.5 + 1.6 io.quarkus.develocity From d4b869bb660d29816b02602cece74f7ab489d64a Mon Sep 17 00:00:00 2001 From: Christian Navolskyi <11958454+ChristianNavolskyi@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:52:20 +0200 Subject: [PATCH 16/16] fix formatting issue (cherry picked from commit 9217fe662a52326338158543629845645b9cbc76) --- .../asciidoc/security-openid-connect-client-reference.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 6bceac124346a..6f73aa97eeadb 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -363,8 +363,7 @@ import io.quarkus.runtime.StartupEvent; import io.smallrye.mutiny.Uni; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; -import jakarta.i -nject.Inject; +import jakarta.inject.Inject; @ApplicationScoped public class OidcClientCreator {