Provide different ClientInterceptor per request using Spring Web Services

3.8k views Asked by At

I've created a custom web service client by extending WebServiceGatewaySupport and also implement custom ClientInterceptor to log some request/response data. I have to create new interceptor for every call because it has to store some data about the request.

The problem occurs when I make two or more calls to my client. The first request applies its own interceptor with its clientId. The second should do the same. But since both requests use the same WebServicetemplate in my client, the second request replaces the interceptor with its own, with its clientId there.

As a result, I should get the following output to the console:

Request: clientId-1
Request: clientId-2
Response: clientId-1
Response: clientId-2

But I got this:

Request: clientId-1
Request: clientId-2
Response: clientId-2
Response: clientId-2

Here is come code examples (just for understanding how it should work):

@Data
class Response {
    private final String result;

    public Response(String result) {
        this.result = result;
    }
}

@Data
class Request {
    private final String firstName;
    private final String lastName;
}

@Data
class Context {
    private final String clientId;
}

@Data
class Client {
    private final String clientId;
    private final String firstName;
    private final String lastName;
}

class CustomInterceptor extends ClientInterceptorAdapter {
    private final String clientId;

    public CustomInterceptor(String clientId) {
        this.clientId = clientId;
    }

    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        System.out.println("Request: " + clientId);
        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        System.out.println("Response: " + clientId);
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        System.out.println("Error: " + clientId);
        return true;
    }
}

@Component
class CustomClient extends WebServiceGatewaySupport {

    public Response sendRequest(Request request, Context context) {
        CustomInterceptor[] interceptors = {new CustomInterceptor(context.getClientId())};
        setInterceptors(interceptors);
        return (Response) getWebServiceTemplate().marshalSendAndReceive(request);
    }
}

@Service
@RequiredArgsConstructor
class CustomService {

    private final CustomClient customClient;

    public String call(Request request, Context context) {
        Response response = customClient.sendRequest(request, context);
        return response.getResult();
    }
}

@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
class CustomController {

    private final CustomService service;

    public CustomController(CustomService service) {
        this.service = service;
    }

    @PostMapping
    public String test(@RequestBody Client client) {
        Request request = new Request(client.getFirstName(), client.getLastName());
        Context context = new Context(client.getClientId());
        return service.call(request, context);
    }
}

Is it possible to implement custom interceptors with some state for each call? Preferably without any locks on WebServicetemplate to avoid performance degradation.

1

There are 1 answers

0
KRonst On BEST ANSWER

Okay. I've found the solution for my case. I've created an implementation of WebServiceMessageCallback and using it I'm saving data of each request not in interceptor but in WebServiceMessage's mime header.

@Data
class CustomMessageCallback implements WebServiceMessageCallback {

    private final String clientId;
    
    @Override
    public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
        MimeHeaders headers = ((SaajSoapMessage) message).getSaajMessage().getMimeHeaders();
        headers.addHeader("X-Client-Id", clientId);
    }
}

And pass this callback in my client implementation:

@Component
class CustomClient extends WebServiceGatewaySupport {

    public Response sendRequest(Request request, Context context) {
        CustomInterceptor[] interceptors = {new CustomInterceptor()};
        setInterceptors(interceptors);
        return (Response) getWebServiceTemplate()
            .marshalSendAndReceive(request, new CustomMessageCallback(context.getClientId()));
    }
}

So now I can get this data while processing request/response/error via interceptor.

@Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
    String clientId = ((SaajSoapMessage) messageContext.getRequest())
        .getSaajMessage()
        .getMimeHeaders()
        .getHeader("X-Client-Id")[0];
    System.out.println("Request: " + clientId);
    return true;
}