diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
index 3de44d4815..1d79c286cf 100644
--- a/ext/microprofile/mp-rest-client/pom.xml
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -94,6 +94,11 @@
jersey-media-sse
${project.version}
+
+ org.mockito
+ mockito-core
+ test
+
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
index d559fb76d3..1f5947e73f 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -31,7 +31,7 @@
* Invokes all interceptors bound to the target.
*
* This approach needs to be used due to CDI does not handle properly interceptor invocation
- * on proxy instances.
+ * on proxy instances. This class is thread safe.
*
* @author David Kral
*/
@@ -42,8 +42,8 @@ class InterceptorInvocationContext implements InvocationContext {
private final Map contextData;
private final List interceptors;
private final WebTarget classLevelWebTarget;
- private Object[] args;
- private int currentPosition;
+ private volatile Object[] args;
+ private final int currentPosition;
/**
* Creates new instance of InterceptorInvocationContext.
@@ -57,14 +57,23 @@ class InterceptorInvocationContext implements InvocationContext {
MethodModel methodModel,
Method method,
Object[] args) {
- this.contextData = new HashMap<>();
this.currentPosition = 0;
+ this.contextData = new HashMap<>();
this.methodModel = methodModel;
this.method = method;
this.args = args;
this.classLevelWebTarget = classLevelWebTarget;
this.interceptors = methodModel.getInvocationInterceptors();
+ }
+ InterceptorInvocationContext(InterceptorInvocationContext other, int currentPosition) {
+ this.currentPosition = currentPosition;
+ this.contextData = other.contextData;
+ this.methodModel = other.methodModel;
+ this.method = other.method;
+ this.args = other.args;
+ this.classLevelWebTarget = other.classLevelWebTarget;
+ this.interceptors = other.interceptors;
}
@Override
@@ -102,10 +111,21 @@ public Map getContextData() {
return contextData;
}
+ /**
+ * This method shall create the next invocation context using {@code position + 1}
+ * and store it in the {@code contextData} map. This is currently used by Helidon's
+ * fault tolerance implementation to get around a problem with CDI's default invocation
+ * context {@code WeldInvocationContextImpl} not correctly supporting async calls.
+ *
+ * @return value returned by intercepted method.
+ */
@Override
public Object proceed() {
+ InvocationContext nextContext = new InterceptorInvocationContext(this, currentPosition + 1);
+ contextData.put(getClass().getName(), nextContext); // accessible to FT interceptor
+
if (currentPosition < interceptors.size()) {
- return interceptors.get(currentPosition++).intercept(this);
+ return interceptors.get(currentPosition).intercept(nextContext);
} else {
return methodModel.invokeMethod(classLevelWebTarget, method, args);
}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
new file mode 100644
index 0000000000..68ae10a18a
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.client.WebTarget;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.glassfish.jersey.microprofile.restclient.InterceptorInvocationContext.InvocationInterceptor;
+
+class InterceptorInvocationContextTest {
+
+ @Test
+ void testContextDataAfterProceed() throws NoSuchMethodException {
+ WebTarget webTarget = Mockito.mock(WebTarget.class);
+ MethodModel methodModel = Mockito.mock(MethodModel.class);
+ InvocationInterceptor invocationInterceptor = Mockito.mock(InvocationInterceptor.class);
+ Mockito.when(methodModel.getInvocationInterceptors())
+ .thenReturn(Collections.singletonList(invocationInterceptor));
+
+ Method method = getClass().getMethod("method");
+ InterceptorInvocationContext c = new InterceptorInvocationContext(
+ webTarget, methodModel, method, new Object[]{});
+
+ Assertions.assertEquals(0, c.getContextData().size());
+ c.proceed();
+ Assertions.assertEquals(1, c.getContextData().size());
+ Assertions.assertTrue(c.getContextData().containsKey(InterceptorInvocationContext.class.getName()));
+ }
+
+ public static Object method() {
+ return null;
+ }
+}