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; + } +}