Search code examples
javaweb-servicesjakarta-eeaopinterceptor

@AroundInvoke interceptor is called twice on a @WebService class


Summary

@AroundInvoke interceptor is called twice on a @WebService class, if the intercepted method is called from outside of the application via endpoint as a SOAP web service.
If the very same method is called internally from another bean, it's called only once (as I would expect).

The intercepted method itself is always called only once!

Question 1: Can I make the interceptor to be called only once?

Question 2: If I cannot, is there a transferable (server independent) way to decide in which interceptor I am, so I can ignore the redundant one?

Question 3: Is this behaviour common (and defined and described in some documentation), or is it dependent on my specific environment (JBoss EAP 6.4.0)?

Observation:

  1. The two calls are not in the same interceptor chain.
  2. It is not the same instance of the interceptor class.
  3. The implementation class of the InvocationContext is different for both the calls.
  4. It's funny that one of the contextData, the InvocationContext's field for passing data along the interceptor chain, is not an instance of the HashMap, but WrappedMessageContext, but it does not wrap the other contextData anyway.

Minimal reproducible code

(I removed the package name.)

MyEndpoint interface

import javax.jws.WebService;

@WebService
public interface MyEndpoint {
    public static final String SERVICE_NAME = "MyEndpointService";
    public String getHello();
}

MyEndpointImpl class

import javax.interceptor.Interceptors;
import javax.jws.WebService;

@WebService(endpointInterface = "MyEndpoint", serviceName = MyEndpoint.SERVICE_NAME)
@Interceptors({TestInterceptor.class})
public class MyEndpointImpl implements MyEndpoint {
    @Override
    public String getHello() {
        System.out.println("MyEndpointImpl.getHello() called");
        return "Hello";
    }
}

TestInterceptor class

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class TestInterceptor {
    @AroundInvoke
    private Object countCalls(InvocationContext ic) throws Exception {
        System.out.println("Interceptor called");
        return ic.proceed();
    }
}

Output

Interceptor called
Interceptor called
MyEndpointImpl.getHello() called

More details

To get more runtime information, I added more logging.

MyEndpointImpl class

import java.lang.reflect.Method;
import java.util.Map;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestInterceptor {
    private static Logger logger = LoggerFactory.getLogger(TestInterceptor.class);
    private static int callCnt = 0;

    @AroundInvoke
    private Object countCalls(InvocationContext ic) throws Exception {
        final String interceptorClass = this.toString();
        final String invocationContextClass = ic.getClass().getName();
        final Method method = ic.getMethod();
        final String calledClass = method.getDeclaringClass().getName();
        final String calledName = method.getName();
        final String message = String.format(
                "%n    INTERCEPTOR: %s%n    InvocationContext: %s%n    %s # %s()",
                interceptorClass, invocationContextClass, calledClass, calledName);
        logger.info(message);

        final int call = ++callCnt;
        final Map<String, Object> contextData = ic.getContextData();
        contextData.put("whoami", call);

        logger.info("BEFORE PROCEED {}, {}", call, contextData);
        final Object ret = ic.proceed();
        logger.info("AFTER PROCEED {}, {}", call, contextData);
        return ret;
    }
}

Output

    INTERCEPTOR: TestInterceptor@74c90b72
    InvocationContext: org.jboss.invocation.InterceptorContext$Invocation
    MyEndpointImpl # getHello()
BEFORE PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext@2cfccb1d
    INTERCEPTOR: TestInterceptor@5226f6d8
    InvocationContext: org.jboss.weld.interceptor.proxy.InterceptorInvocationContext
    MyEndpointImpl # getHello()
BEFORE PROCEED 2, {whoami=2}
MyEndpointImpl.getHello() called
AFTER PROCEED 2, {whoami=2}
AFTER PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext@2cfccb1d

Solution

  • I cannot answer your questions directly, but maybe some clarification about the contexts may help you.

    The Java EE JAX-WS implementation varies from server to server. For Example Glassfish uses Metro and JBoss uses Apache CXF.

    There are different kind of interceptors chains which allow to control programmatically the conditions before and after the request/response processing.

    The interceptors for the SOAP web service calls are SOAP handlers and logical handlers (See Oracle documentation). Both can access SOAP message on different levels (the whole or only the payload).

    My assumption is that your the interceptor called twice, once for accessing through HTTP/SOAP, and once for access over RMI.

    In the first interceptor invocation, what you see as context is org.apache.cxf.jaxws.context.WrappedMessageContext which is a Map implementation. See WarppedMessageContext, Apache CXF web service context. It is invoked for HTTP/SOAP access.

    The second invocation is what you expect when using the RMI (probably triggered from Apache CXF, after the SOAP message is processed).

    To avoid this you can use third class for logic implementation with interceptor defined. The existing web service implementation class will only delegate to it and will not contain interceptor annotation anymore.

    Example code can be seen here: OSCM Project