2

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.

KRonst
  • 21
  • 2
  • 4
  • Did you add this interceptor in interceptor registry? – prem30488 Oct 21 '20 at 09:42
  • No, I haven't. Current implementation works fine until I send two or more requests in the same time. – KRonst Oct 21 '20 at 10:02
  • `ClientInterceptorAdapter` it's a default implementation of `ClientInterceptor`. I use this just to not holding empty method `afterCompletion` in my `CustomInterceptor` – KRonst Oct 21 '20 at 10:33
  • What are imports for context and request in CustomClient? – prem30488 Oct 21 '20 at 11:25
  • Sorry, I've updated sample of code to show that I'm saving to interceptor data not from the object that sending via `WebServiceTemplate` but from another object named `context`. So the data that needs to be processed in the interceptor doesn't went into the `WebServiceTemplate` and I can't get it from the `MessageContext`. – KRonst Oct 21 '20 at 11:25
  • what is import for `Context` in `CustomClient.java`? – prem30488 Oct 21 '20 at 11:33
  • It's a custom class that holds some metadata about processing current request (`clientId`, `traceId` etc) – KRonst Oct 21 '20 at 11:36
  • How CustomClient is being called? Any sample code? I am trying to generate issue in my code that you are facing, – prem30488 Oct 21 '20 at 11:43
  • Simply `customClient.sendRequest(request, context);` – KRonst Oct 21 '20 at 11:48
  • how do you construct request and context, provide sample.Edit it in your question. – prem30488 Oct 21 '20 at 11:50
  • Okay. I've added some more examples of code. This is just a dummy model of my real project – KRonst Oct 21 '20 at 12:07
  • java.lang.IllegalArgumentException: 'uri' must not be empty – prem30488 Oct 21 '20 at 12:56

1 Answers1

0

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;
}
KRonst
  • 21
  • 2
  • 4