Access Spring WS MessageContext from anywhere

2009-11-07

UPDATE (2011-03-27): This article is relevant to those using the 1.5 version of Spring WS. Since version 2.0 it is trivial to access MessageContext by simply declaring it as one of the arguments in the handling method.

Spring Web Services framework has a special MessageContext class for holding request and response messages as well as arbitrary properties during the processing of a WS request. This class is especially useful if you are writing your own endpoint interceptors, because it allows you to do whatever you want with all incoming or outgoing web service messages, regardless of the endpoint in which they are processed.

There is certainly no problem getting to MessageContext in an EndpointInterceptor (all interface methods contain a MessageContext parameter), but what if you need to access it in your endpoint handler? For example, if you had to know the content type of all SOAP attachments that came in request, you would do it like this:

SoapMessage request = (SoapMessage) messageContext.getRequest();
for (Iterator it = request.getAttachments(); it.hasNext();) {
    Attachment attachment = (Attachment) it.next();
    System.out.println(attachment.getContentType());
}

You could do that in a MessageEndpoint, but not in a PayloadEndpoint where you have no access to MessageContext since it is not passed as parameter to your endpoint method.

Curiously, TransportContext does not have this limitation, as it is bound to current thread and accessible via TransportContextHolder.getTransportContext(). Users of Spring Security will remember that SecurityContext can be accessed in the same manner via SecurityContextHolder.getContext(). Notice a recurring theme here? What we lack in Spring WS is a “MessageContextHolder” class that would allow us to access MessageContext from anywhere in our code. Let’s go ahead and implement this missing utility.

MessageContextHolder is going to be a really simple utility class that can store and retrieve a reference to MessageContext in local thread.

public final class MessageContextHolder {

    private static ThreadLocal<MessageContext> threadLocal =
        new ThreadLocal<MessageContext>() {
            @Override
            protected MessageContext initialValue() {
                return null;
            }
        };

    private MessageContextHolder() {
    }

    public static MessageContext getMessageContext() {
        return threadLocal.get();
    }

    public static void setMessageContext(MessageContext context) {
        threadLocal.set(context);
    }

    public static void removeMessageContext() {
        threadLocal.remove();
    }
}

Next we need an endpoint interceptor that puts MessageContext to “Holder” before WS request is processed, and removes it when WS response (or fault) is about to be sent.

public class MessageContextHolderInterceptor implements EndpointInterceptor {

    public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
        MessageContextHolder.setMessageContext(messageContext);
        return true;
    }

    public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
        MessageContextHolder.removeMessageContext();
        return true;
    }

    public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
        MessageContextHolder.removeMessageContext();
        return true;
    }
}

Finally, we configure whatever EndpointMapping beans we use to include our interceptor:

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
    <property name="interceptors">
        <list>
            <bean class="my.project.MessageContextHolderInterceptor"/>
        </list>
    </property>
</bean>

Now once this interceptor does its job upon each request, you can say MessageContextHolder.getMessageContext() and you’ll get current MessageContext whenever you need it.

  1. Michael Che Mafor - January 20th, 2010 at 20:29

    This is what I’ve been searching for a while. Many thanks for your ingenuity.

  2. Osvaldas Grigas - January 24th, 2010 at 20:26

    I’m glad someone found it useful :)

  3. Vijay - April 12th, 2010 at 22:22

    Thanks for the post. It was exactly the solution I was looking for.

    Thanks once again.

  4. Val - February 24th, 2011 at 08:34

    Thanks a lot, very useful. But i’ve had to declare interceptor like that:

    <sws:interceptors>
    <bean class=”ru.rjd.ws.dsi.util.MessageContextHolderInterceptor”/>
    </sws:interceptors>

    cause in your example it didn’t intercept at all for some reason.

  5. Chaitanya - March 25th, 2011 at 15:30

    I think this is more of a hack…. Is there any cleaner way to get the MessageContext in endpoint?
    Other than wrapper endpoint?

  6. Osvaldas Grigas - March 27th, 2011 at 21:10

    @Chaitanya
    Since version 2.0 you can simply declare MessageContext as one of the method’s parameters. This article was written at the time when 1.5 was the latest version.