diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/CustomContainerResponseFilter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/CustomContainerResponseFilter.java
index 5490af21c2b1e..c45a1d5430757 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/CustomContainerResponseFilter.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/CustomContainerResponseFilter.java
@@ -1,10 +1,12 @@
package io.quarkus.resteasy.reactive.server.test.customproviders;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.reactive.server.ServerResponseFilter;
import org.jboss.resteasy.reactive.server.SimpleResourceInfo;
@@ -15,10 +17,11 @@ public class CustomContainerResponseFilter {
@ServerResponseFilter
public void whatever(SimpleResourceInfo simplifiedResourceInfo, ContainerResponseContext responseContext,
- ContainerRequestContext requestContext, Throwable t) {
+ ContainerRequestContext requestContext, UriInfo uriInfo, Throwable t) {
assertTrue(
PreventAbortResteasyReactiveContainerRequestContext.class.isAssignableFrom(requestContext.getClass()));
assertNull(t);
+ assertNotNull(uriInfo);
if (simplifiedResourceInfo != null) {
responseContext.getHeaders().putSingle("java-method", simplifiedResourceInfo.getMethodName());
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java
new file mode 100644
index 0000000000000..87eeae28e6a13
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java
@@ -0,0 +1,69 @@
+package io.quarkus.rest.client.reactive;
+
+import static io.restassured.RestAssured.when;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class MediaTypeSuffixTest {
+
+ private static final String CUSTOM_JSON_MEDIA_TYPE = "application/vnd.search.v1+json";
+
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(HelloResource.class, Client.class))
+ .withConfigurationResource("media-type-suffix-application.properties");
+
+ @Test
+ public void test() {
+ when()
+ .get("/hello")
+ .then()
+ .statusCode(200)
+ .body("foo", is("bar"));
+ }
+
+ @RegisterRestClient(configKey = "test")
+ @Path("/hello")
+ public interface Client {
+
+ @GET
+ @Path("/custom")
+ @Produces(CUSTOM_JSON_MEDIA_TYPE)
+ Map
test();
+ }
+
+ @Path("/hello")
+ public static class HelloResource {
+
+ private final Client client;
+
+ public HelloResource(@RestClient Client client) {
+ this.client = client;
+ }
+
+ @GET
+ @Path("/custom")
+ @Produces(CUSTOM_JSON_MEDIA_TYPE)
+ public Map hello() {
+ return Map.of("foo", "bar");
+ }
+
+ @GET
+ public Map test() {
+ return client.test();
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/media-type-suffix-application.properties b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/media-type-suffix-application.properties
new file mode 100644
index 0000000000000..f37c300de6005
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/media-type-suffix-application.properties
@@ -0,0 +1 @@
+quarkus.rest-client.test.url=http://localhost:${quarkus.http.test-port:8081}
diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
index 7e08b1f9d3af1..59ed540f1c249 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java
@@ -477,9 +477,12 @@ private void flushTag() {
// Parameter declaration
// {@org.acme.Foo foo}
Scope currentScope = scopeStack.peek();
- int spaceIdx = content.indexOf(" ");
- String key = content.substring(spaceIdx + 1, content.length());
- String value = content.substring(1, spaceIdx);
+ String[] parts = content.substring(1).trim().split("[ ]{1,}");
+ if (parts.length != 2) {
+ throw parserError("invalid parameter declaration " + START_DELIMITER + buffer.toString() + END_DELIMITER);
+ }
+ String value = parts[0];
+ String key = parts[1];
currentScope.putBinding(key, Expressions.typeInfoFrom(value));
sectionStack.peek().currentBlock().addNode(new ParameterDeclarationNode(content, origin(0)));
} else {
diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
index aba4d4f4e6357..1a12c2952f0c3 100644
--- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
+++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java
@@ -358,6 +358,20 @@ public void testInvalidBracket() {
"Parser error on line 1: invalid bracket notation expression in {foo.baz[}", 1);
}
+ @Test
+ public void testInvalidParamDeclaration() {
+ assertParserError("{@com.foo }",
+ "Parser error on line 1: invalid parameter declaration {@com.foo }", 1);
+ assertParserError("{@ com.foo }",
+ "Parser error on line 1: invalid parameter declaration {@ com.foo }", 1);
+ assertParserError("{@com.foo.Bar bar baz}",
+ "Parser error on line 1: invalid parameter declaration {@com.foo.Bar bar baz}", 1);
+ assertParserError("{@}",
+ "Parser error on line 1: invalid parameter declaration {@}", 1);
+ assertParserError("{@\n}",
+ "Parser error on line 1: invalid parameter declaration {@\n}", 1);
+ }
+
public static class Foo {
public List- getItems() {
diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java
index 55815186fa959..c73f288f0c87a 100644
--- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java
+++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java
@@ -17,6 +17,7 @@
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;
import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap;
+import org.jboss.resteasy.reactive.common.util.MediaTypeHelper;
public class ClientReaderInterceptorContextImpl extends AbstractClientInterceptorContextImpl
implements ReaderInterceptorContext {
@@ -34,7 +35,7 @@ public ClientReaderInterceptorContextImpl(Annotation[] annotations, Class> ent
Map properties, MultivaluedMap headers,
ConfigurationImpl configuration, Serialisers serialisers, InputStream inputStream,
ReaderInterceptor[] interceptors) {
- super(annotations, entityClass, entityType, mediaType, properties);
+ super(annotations, entityClass, entityType, MediaTypeHelper.withSuffixAsSubtype(mediaType), properties);
this.configuration = configuration;
this.serialisers = serialisers;
this.inputStream = inputStream;
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java
index 422de499dd6e3..41b72877f9808 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java
@@ -272,6 +272,9 @@ public static boolean isUnsupportedWildcardSubtype(MediaType mediaType) {
* that uses the suffix as the subtype
*/
public static MediaType withSuffixAsSubtype(MediaType mediaType) {
+ if (mediaType == null) {
+ return null;
+ }
int plusIndex = mediaType.getSubtype().indexOf('+');
if ((plusIndex > -1) && (plusIndex < mediaType.getSubtype().length() - 1)) {
mediaType = new MediaType(mediaType.getType(),
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerResponseFilter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerResponseFilter.java
index daf7fa44c1e1c..4247098221019 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerResponseFilter.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerResponseFilter.java
@@ -8,6 +8,7 @@
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
/**
* When used on a method, then an implementation of {@link javax.ws.rs.container.ContainerResponseContext} is generated
@@ -41,6 +42,7 @@
* {@link ContainerRequestContext}
* {@link ContainerResponseContext}
* {@link ResourceInfo}
+ * {@link UriInfo}
* {@link SimpleResourceInfo}
* {@link Throwable} - The thrown exception - or {@code null} if no exception was thrown
*
diff --git a/integration-tests/devmode/pom.xml b/integration-tests/devmode/pom.xml
index a279fdddaf513..5fedcbc12fe25 100644
--- a/integration-tests/devmode/pom.xml
+++ b/integration-tests/devmode/pom.xml
@@ -36,7 +36,7 @@
io.quarkus
- quarkus-qute-deployment
+ quarkus-resteasy-reactive-qute-deployment
test
diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteErrorPageTest.java b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteErrorPageTest.java
new file mode 100644
index 0000000000000..be9814850f204
--- /dev/null
+++ b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteErrorPageTest.java
@@ -0,0 +1,29 @@
+package io.quarkus.test.qute;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusDevModeTest;
+import io.restassured.RestAssured;
+
+public class QuteErrorPageTest {
+
+ @RegisterExtension
+ static final QuarkusDevModeTest config = new QuarkusDevModeTest()
+ .withApplicationRoot(
+ root -> root.addAsResource(new StringAsset("{hello.foo}"), "templates/hello.txt"));
+
+ @Test
+ public void testErrorPage() {
+ config.modifyResourceFile("templates/hello.txt", file -> "{@java.lang.String hello}{hello.foo}");
+ RestAssured.when().get("/hello").then()
+ .body(containsString("Incorrect expression found: {hello.foo}"))
+ .statusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+
+}
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
index bfc8c6d251456..fa25471402d78 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java
@@ -151,6 +151,16 @@ public void canRunTest() throws Exception {
assertThat(buildResult.getTasks().get(":test")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
}
+ @Test
+ public void generateCodeBeforeTests() throws Exception {
+ createProject(SourceType.JAVA);
+
+ BuildResult firstBuild = runGradleWrapper(projectRoot, "test", "--stacktrace");
+ assertThat(firstBuild.getOutput()).contains("Task :quarkusGenerateCode");
+ assertThat(firstBuild.getOutput()).contains("Task :quarkusGenerateCodeTests");
+ assertThat(firstBuild.getTasks().get(":test")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
+ }
+
private void createProject(SourceType sourceType) throws Exception {
Map context = new HashMap<>();
context.put("path", "/greeting");
diff --git a/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/AddressDao.kt b/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/AddressDao.kt
index 3193271864633..f10ac5da7b6e5 100644
--- a/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/AddressDao.kt
+++ b/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/AddressDao.kt
@@ -4,7 +4,7 @@ import io.quarkus.hibernate.orm.panache.kotlin.PanacheRepositoryBase
import javax.enterprise.context.ApplicationScoped
@ApplicationScoped
-open class AddressDao : PanacheRepositoryBase {
+open class AddressDao(private val dummyService: DummyService) : PanacheRepositoryBase {
companion object {
fun shouldBeOverridden(): Nothing {
throw UnsupportedOperationException("this should be called and not be overwritten by the quarkus plugin")
@@ -12,4 +12,4 @@ open class AddressDao : PanacheRepositoryBase {
}
override fun count(query: String, params: Map): Long = shouldBeOverridden()
-}
\ No newline at end of file
+}
diff --git a/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/DummyService.kt b/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/DummyService.kt
new file mode 100644
index 0000000000000..85c330413fdb7
--- /dev/null
+++ b/integration-tests/hibernate-orm-panache-kotlin/src/main/kotlin/io/quarkus/it/panache/kotlin/DummyService.kt
@@ -0,0 +1,8 @@
+package io.quarkus.it.panache.kotlin
+
+import javax.enterprise.context.ApplicationScoped
+
+// used only to validate that we can inject CDI beans into Panache repositories written in Kotlin
+@ApplicationScoped
+class DummyService {
+}
diff --git a/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/SerializationTestEndpoint.java b/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/SerializationTestEndpoint.java
new file mode 100644
index 0000000000000..f7ceff23e79ab
--- /dev/null
+++ b/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/SerializationTestEndpoint.java
@@ -0,0 +1,41 @@
+package io.quarkus.example.jpaoracle;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(name = "JPATestOracleSerialization", urlPatterns = "/jpa-oracle/testserialization")
+public class SerializationTestEndpoint extends HttpServlet {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ try {
+ final String output = serializedstring();
+ resp.getWriter().write(output);
+
+ } catch (Exception e) {
+ resp.getWriter().write("An error occurred while attempting serialization operations");
+ }
+ }
+
+ private String serializedstring() throws IOException, ClassNotFoundException {
+ byte[] bytes = null;
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+ oos.writeObject("Hello from Serialization Test");
+ oos.flush();
+ bytes = baos.toByteArray();
+ }
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bais)) {
+ return (String) ois.readObject();
+ }
+ }
+}
diff --git a/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java b/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java
index b871fd14d1bfa..ad3fe68f84c7c 100644
--- a/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java
+++ b/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java
@@ -20,4 +20,9 @@ public void testJPAFunctionalityFromServlet() throws Exception {
RestAssured.when().get("/jpa-oracle/testfunctionality").then().body(is("OK"));
}
+ @Test
+ public void testSerializationFromServlet() throws Exception {
+ RestAssured.when().get("/jpa-oracle/testserialization").then().body(is("Hello from Serialization Test"));
+ }
+
}
diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt
index c2d2bebd4c4e6..30e6438eac573 100644
--- a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt
+++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt
@@ -4,4 +4,4 @@ import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository
import javax.enterprise.context.ApplicationScoped
@ApplicationScoped
-class BookRepository : PanacheMongoRepository
\ No newline at end of file
+class BookRepository(private val dummyService: DummyService) : PanacheMongoRepository
diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/DummyService.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/DummyService.kt
new file mode 100644
index 0000000000000..eed33d52a1ba8
--- /dev/null
+++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/DummyService.kt
@@ -0,0 +1,8 @@
+package io.quarkus.it.mongodb.panache.book
+
+import javax.enterprise.context.ApplicationScoped
+
+// used only to validate that we can inject CDI beans into Panache repositories written in Kotlin
+@ApplicationScoped
+class DummyService {
+}
diff --git a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java
index a2ca7c1211688..ea8b059a72201 100644
--- a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java
+++ b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java
@@ -50,7 +50,7 @@ public Map start() {
.aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON)
.withBody(
- "{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));
+ "{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_2\", \"refresh_expires_in\":1}")));
server.stubFor(WireMock.post("/refresh-token-only")
.withRequestBody(matching("grant_type=refresh_token&refresh_token=shared_refresh_token"))
diff --git a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java
index f5a955b8549a4..0a274ca502f40 100644
--- a/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java
+++ b/integration-tests/oidc-client-wiremock/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java
@@ -30,12 +30,41 @@ public class OidcClientTest {
@Test
public void testEchoAndRefreshTokens() {
+ // access_token_1 and refresh_token_1 are acquired using a password grant request.
+ // access_token_1 expires in 4 seconds, refresh_token_1 has no lifespan limit as no `refresh_expires_in` property is returned.
+ // "Default OidcClient has acquired the tokens" record is added to the log
RestAssured.when().get("/frontend/echoToken")
.then()
.statusCode(200)
.body(equalTo("access_token_1"));
- // Wait until the access token has expired
+ // Wait until the access_token_1 has expired
+ waitUntillAccessTokenHasExpired();
+
+ // access_token_1 has expired, refresh_token_1 is assumed to be valid and used to acquire access_token_2 and refresh_token_2.
+ // access_token_2 expires in 4 seconds, but refresh_token_2 - in 1 sec - it will expire by the time access_token_2 has expired
+ // "Default OidcClient has refreshed the tokens" record is added to the log
+ RestAssured.when().get("/frontend/echoToken")
+ .then()
+ .statusCode(200)
+ .body(equalTo("access_token_2"));
+
+ // Wait until the access_token_2 has expired
+ waitUntillAccessTokenHasExpired();
+
+ // Both access_token_2 and refresh_token_2 have now expired therefore a password grant request is repeated,
+ // as opposed to using a refresh token grant.
+ // access_token_1 is returned again - as the same token URL and grant properties are used and Wiremock stub returns access_token_1
+ // 2nd "Default OidcClient has acquired the tokens" record is added to the log
+ RestAssured.when().get("/frontend/echoToken")
+ .then()
+ .statusCode(200)
+ .body(equalTo("access_token_1"));
+
+ checkLog();
+ }
+
+ private static void waitUntillAccessTokenHasExpired() {
long expiredTokenTime = System.currentTimeMillis() + 5000;
await().atMost(10, TimeUnit.SECONDS)
.pollInterval(Duration.ofSeconds(3))
@@ -45,12 +74,6 @@ public Boolean call() throws Exception {
return System.currentTimeMillis() > expiredTokenTime;
}
});
-
- RestAssured.when().get("/frontend/echoToken")
- .then()
- .statusCode(200)
- .body(equalTo("access_token_2"));
- checkLog();
}
@Test
@@ -109,8 +132,8 @@ public void run() throws Throwable {
}
}
- assertEquals(1, tokenAcquisitionCount,
- "Log file must contain a single OidcClientImpl token acquisition confirmation");
+ assertEquals(2, tokenAcquisitionCount,
+ "Log file must contain two OidcClientImpl token acquisition confirmations");
assertEquals(1, tokenRefreshedCount,
"Log file must contain a single OidcClientImpl token refresh confirmation");
}
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java
index cd68880ea8552..e11031259b77e 100644
--- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java
@@ -4,6 +4,7 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;
+import io.quarkus.oidc.runtime.DefaultTokenIntrospectionUserInfoCache;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
@@ -14,8 +15,11 @@ public class CodeFlowResource {
@Inject
SecurityIdentity identity;
+ @Inject
+ DefaultTokenIntrospectionUserInfoCache tokenCache;
+
@GET
public String access() {
- return identity.getPrincipal().getName();
+ return identity.getPrincipal().getName() + ", cache size: " + tokenCache.getCacheSize();
}
}
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
index 90e2ced96f7ba..987850cb62ad1 100644
--- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
@@ -5,6 +5,7 @@
import javax.ws.rs.Path;
import io.quarkus.oidc.UserInfo;
+import io.quarkus.oidc.runtime.DefaultTokenIntrospectionUserInfoCache;
import io.quarkus.security.Authenticated;
@Path("/code-flow-user-info")
@@ -14,8 +15,14 @@ public class CodeFlowUserInfoResource {
@Inject
UserInfo userInfo;
+ @Inject
+ DefaultTokenIntrospectionUserInfoCache tokenCache;
+
@GET
public String access() {
- return userInfo.getString("preferred_username");
+ int cacheSize = tokenCache.getCacheSize();
+ tokenCache.clearCache();
+ return userInfo.getString("preferred_username") + ", cache size: "
+ + cacheSize;
}
}
diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties
index 347269c186f0a..1b76d8fb9ec01 100644
--- a/integration-tests/oidc-wiremock/src/main/resources/application.properties
+++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties
@@ -27,11 +27,15 @@ quarkus.oidc.code-flow-user-info-only.client-id=quarkus-web-app
quarkus.oidc.code-flow-user-info-only.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
quarkus.oidc.code-flow-user-info-only.application-type=web-app
+quarkus.oidc.token-cache.max-size=1
+
quarkus.oidc.bearer.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.bearer.client-id=quarkus-app
quarkus.oidc.bearer.credentials.secret=secret
quarkus.oidc.bearer.authentication.scopes=profile,email,phone
quarkus.oidc.bearer.token.audience=https://service.example.com
+quarkus.oidc.bearer.token.audience=https://service.example.com
+quarkus.oidc.bearer.allow-token-introspection-cache=false
quarkus.oidc.bearer-no-introspection.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.bearer-no-introspection.client-id=quarkus-app
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
index 68d365b2ada44..ae4d1dac27b19 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
@@ -39,7 +39,7 @@ public void testCodeFlow() throws IOException {
page = form.getInputByValue("login").click();
- assertEquals("alice", page.getBody().asText());
+ assertEquals("alice, cache size: 0", page.getBody().asText());
}
}
@@ -56,7 +56,7 @@ public void testCodeFlowUserInfo() throws IOException {
page = form.getInputByValue("login").click();
- assertEquals("alice", page.getBody().asText());
+ assertEquals("alice, cache size: 1", page.getBody().asText());
}
}
diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
index cd0e50dd00529..ddc0a3a2e6a58 100644
--- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
+++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java
@@ -74,33 +74,6 @@ public void start() throws IOException {
}
}
- if (devServicesLaunchResult.manageNetwork() && (devServicesLaunchResult.networkId() != null)) {
- try {
- int networkCreateResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD)
- .command(DOCKER_BINARY, "network", "create", devServicesLaunchResult.networkId()).start().waitFor();
- if (networkCreateResult > 0) {
- throw new RuntimeException("Creating container network '" + devServicesLaunchResult.networkId()
- + "' completed unsuccessfully");
- }
- // do the cleanup in a shutdown hook because there might be more services (launched via QuarkusTestResourceLifecycleManager) connected to the network
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD)
- .command(DOCKER_BINARY, "network", "rm", devServicesLaunchResult.networkId()).start()
- .waitFor();
- } catch (InterruptedException | IOException ignored) {
- System.out.println(
- "Unable to delete container network '" + devServicesLaunchResult.networkId() + "'");
- }
- }
- }));
- } catch (InterruptedException e) {
- throw new RuntimeException("Unable to pull container image '" + containerImage + "'", e);
- }
- }
-
System.setProperty("test.url", TestHTTPResourceManager.getUri());
if (httpPort == 0) {
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java
index 36dd9ee032936..b253d9d039cdb 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java
@@ -2,6 +2,7 @@
import static io.quarkus.test.common.PathTestHelper.getAppClassLocationForTestLocation;
import static io.quarkus.test.common.PathTestHelper.getTestClassesLocation;
+import static java.lang.ProcessBuilder.Redirect.DISCARD;
import java.io.File;
import java.io.FileInputStream;
@@ -58,6 +59,8 @@ public final class IntegrationTestUtil {
public static final int DEFAULT_HTTPS_PORT = 8444;
public static final long DEFAULT_WAIT_TIME_SECONDS = 60;
+ private static final String DOCKER_BINARY = "docker";
+
private IntegrationTestUtil() {
}
@@ -304,7 +307,43 @@ public void accept(String s, String s2) {
}
}
- return new DefaultDevServicesLaunchResult(propertyMap, networkId, manageNetwork, curatedApplication);
+ DefaultDevServicesLaunchResult result = new DefaultDevServicesLaunchResult(propertyMap, networkId, manageNetwork,
+ curatedApplication);
+ createNetworkIfNecessary(result);
+ return result;
+ }
+
+ // this probably isn't the best place for this method, but we need to create the docker container before
+ // user code is aware of the network
+ private static void createNetworkIfNecessary(
+ final ArtifactLauncher.InitContext.DevServicesLaunchResult devServicesLaunchResult) {
+ if (devServicesLaunchResult.manageNetwork() && (devServicesLaunchResult.networkId() != null)) {
+ try {
+ int networkCreateResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD)
+ .command(DOCKER_BINARY, "network", "create", devServicesLaunchResult.networkId()).start().waitFor();
+ if (networkCreateResult > 0) {
+ throw new RuntimeException("Creating container network '" + devServicesLaunchResult.networkId()
+ + "' completed unsuccessfully");
+ }
+ // do the cleanup in a shutdown hook because there might be more services (launched via QuarkusTestResourceLifecycleManager) connected to the network
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD)
+ .command(DOCKER_BINARY, "network", "rm", devServicesLaunchResult.networkId()).start()
+ .waitFor();
+ } catch (InterruptedException | IOException ignored) {
+ System.out.println(
+ "Unable to delete container network '" + devServicesLaunchResult.networkId() + "'");
+ }
+ }
+ }));
+ } catch (Exception e) {
+ throw new RuntimeException("Creating container network '" + devServicesLaunchResult.networkId()
+ + "' completed unsuccessfully");
+ }
+ }
}
static void activateLogging() {