Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@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
like image 903
Honza Zidek Avatar asked Nov 10 '15 22:11

Honza Zidek


2 Answers

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

like image 124
S.Stavreva Avatar answered Nov 11 '22 17:11

S.Stavreva


I had the exact same problem and found a solution.

If you instead of using the @Interceptors style binding use a @InterceptorBinding style binding, then the interceptor is only instantiated and invoked once (at least in my case on WildFly 10.1.0.Final).

This is what your example would look like using @InterceptorBinding style.

Your custom interceptor binding annotation:

import javax.interceptor.InterceptorBinding;
...

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface MyInterceptorBinding {

Your endpoint:

@WebService(endpointInterface = "MyEndpoint", serviceName = 
MyEndpoint.SERVICE_NAME)
@MyInterceptorBinding
public class MyEndpointImpl implements MyEndpoint {

Your interceptor:

import javax.interceptor.Interceptor;
import javax.annotation.Priority;
...

@Interceptor
@MyInterceptorBinding
@Priority(Interceptor.Priority.APPLICATION) //we use @Priority to enable this interceptor application-wide, without having to use beans.xml in every module.
public class TestInterceptor {
    @AroundInvoke
    private Object countCalls(InvocationContext ic) throws Exception {
        System.out.println("Interceptor called");
        return ic.proceed();
    }

I never figured out exactly what the problem is, but I suspect that the @Interceptors style binding is valid for multiple types of interceptors (EJB and CDI) while the @InterceptorBinding style is maybe only valid for CDI interceptors. Maybe a JAX-WS @WebService is both an EJB and a CDI bean?

like image 31
TueCN Avatar answered Nov 11 '22 16:11

TueCN