Filtering response using webclient - java

I'm new using webclient to cosume a Rest API and I want to know how can I filter a response to match only what I want.
So I have this endpoint which brings me a customerById but I need to show only the the systemsId = 100
Let me show:
#GetMapping("/getCucoId/{cucoId}")
public Mono<CuCoPerson> getCucoRelationById(#PathVariable Integer cucoId) {
WebClient webClient = WebClient.create();
return webClient.get()
.uri(GET_RELATION_BY_ID + cucoId)
.header("Accept", "application/json")
.header("Authorization", "Bearer eyJraWQiOi.....")
.retrieve()
.bodyToMono(CuCoPerson.class);
}
And the POJO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class CuCoPerson {
private Integer cucoID;
private List<CustomerRelation> relatedCustomers;
}
And this is the response in Postman:
{
"cucoID": 100288298,
"relatedCustomers": [
{
"customerId": "F6305957",
"systemId": 100
},
{
"customerId": "F8364917",
"systemId": 400
},
{
"customerId": "F4194868",
"systemId": 101
}
]
}
So I need only to show the relatedCustomers who only have a systemID = 100
Thanks in advance!

I think you are looking for either Mono.map(), or Mono.flatMap().
Remove the customers, who don't match in the function, and return the changed object.
#GetMapping("/getCucoId/{cucoId}")
public Mono<CuCoPerson> getCucoRelationById(#PathVariable Integer cucoId) {
WebClient webClient = WebClient.create();
return webClient.get()
.uri(GET_RELATION_BY_ID + cucoId)
.header("Accept", "application/json")
.header("Authorization", "Bearer eyJraWQiOi.....")
.retrieve()
.bodyToMono(CuCoPerson.class)
.map(cuCoPerson -> {
List<CustomerRelation> matches = cuCoPerson.getRelatedCustomers()
.stream()
.filter(relation -> relation.getSystemId().equals(100))
.collect(Collectors.toList());
cuCoPerson.setRelatedCustomers(matches);
return cuCoPerson;
});
}
As mentioned in other answers, doing this kind of filtering on client side is bad practice. If possible the API should expose parameter for systemId and return only the data you need.

First of all this is bad Backend design. Since you (client side) have the need for such info you should have an endpoint available to get your info by customer ID and System ID. Data filtering should be done on the backend and the client should remain as thin as possible. Otherwise you perform logic both on server-side and client-side plus you send some completely unneeded info over the network and then filter it out on the client side. But if that's your available resources and you can't change the server-side then you you take your JSON response, and parse it into your class that maps to that JSON or just into Map<String,Object> if you don't have the class for that JSON and filter it out yourself. To parse a json string to map or a specific POJO you can use Jackson library - method readValue() of ObjectMapper Or Gson library. Also, I wrote my own simple wrapper over Jackson library that simplifies the use. Class JsonUtils is available as part of MgntUtils library (written and maintained by me). This class just has the methods that allow you to parse and de-serialize from/to Json String from/to a class instance. Here is a simple example on how to parse your JSON string into a Map<String,Object> with JSONUtils class
Map<String,Object> myMap
try {
myMap = JsonUtils.readObjectFromJsonString(myJsonStr, Map.class);
} catch(IOException ioe) {
....
}
MgntUtils library available as Maven artifact and on Github (including source code and Javadoc)

it could be something like this:
return webClient.get()
.uri("/someUrl")
.header("Accept", "application/json")
.header("Authorization", "Bearer eyJraWQiOi.....")
.retrieve()
.bodyToFlux(CuCoPerson.class)
.filter(cuCoPerson -> cuCoPerson.getRelatedCustomers().stream().anyMatch(cr -> cr.getSystemId() == 100))
.take(1)
.next();
But this has disadvantage that you are filtering the results on client side (your application). I would try to ask the api provider if s/he is able to provide filter parameter (something like systemId) and in that case you would call the endpoint with that query param.

Related

How to Retrieve OAuth2AuthorizedClient in a request when using WebFlux

I have a back-end(Springboot) application that is connected to Azure AD and a front-end application that accesses it. In the front-end, I am requiring the user to authenticate using MSAL and passing this authentication to the BE using the On-Behalf-Of flow.
In the front-end, when I am trying to specify the registered client I simple use:
#RegisteredOAuth2AuthorizedClient("back-end") OAuth2AuthorizedClient authorizedClient
I'm trying to create another back-end application that my existing back-end will call and pass the authentication using OBO flow. To check the difference between the initial token from the user and the token the BE will provide to the new BE application, I created a log that fetch the token from these client like authorizedClient.getAccessToken().getTokenValue().
Now that I don't want the explicit approach and want only to add directly in the webclient request the .attributes(clientRegistrationId("new-back-end")), is there any way to check the token? Or at least get the OAuth2AuthorizedClient from the request?
Sample code:
webClient.get()
.uri(new URI(resourceBaseUri + resourceEndpoint))
.attributes(clientRegistrationId("new-be-app"))
.retrieve()
.bodyToMono(String.class)
.block();
• You can do the same as desired by you by using the ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’ to determine the client to use by resolving the ‘OAuth2AuthorizedClient’ from the ‘ClientRequest.attributes()’. The following code shows how to set an ‘OAuth2AuthorizedClient’ as a request attribute: -
#GetMapping("/")
public Mono<String> index(#RegisteredOAuth2AuthorizedClient("okta")
OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
Note: - ‘oauth2AuthorizedClient()’ is a static method in ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’.
Also, please note that the following code shows how to set the ‘ClientRegistration.getRegistrationId()’ as a request attribute: -
#GetMapping("/")
public Mono<String> index() {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta"))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
You can use the code below also for your purpose: -
#Component
#RequiredArgsConstructor
public class OAuth2Utils {
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
public Mono<OAuth2AuthorizedClient> extractOAuth2AuthorizedClient(ServerRequest request) {
return request.principal()
.filter(principal -> principal instanceof OAuth2AuthenticationToken)
.cast(OAuth2AuthenticationToken.class)
.flatMap(auth -> authorizedClientRepository.loadAuthorizedClient(auth.getAuthorizedClientRegistrationId(), auth, request.exchange()));
}
}
Please find the links below for more information: -
How to access OAuth2AuthorizedClient in Webflux Functional Endpoints?
https://docs.spring.io/spring-security/reference/reactive/oauth2/client/authorized-clients.html#_providing_the_authorized_client

Java Spring Boot Webflux - Mono response when there is no http body, but just http status

Small question regarding Spring Boot Webflux 2.5.0 and how to deal with a http response without body.
By "without body" I mean:
For instance, a web application I consume the rest API and have no control returns:
HTTP status code 200
HTTP body {"foo": "bar"}
With Spring Webflux, we can easily write something like:
public Mono<FooBar> sendRequest(SomeRequest someRequest) {
return webClient.mutate()
.baseUrl("https://third-party-rest-api.com:443")
.build()
.post()
.uri(/someroute)
.body(BodyInserters.fromValue(someRequest))
.retrieve().bodyToMono(FooBar.class);
}
public class FooBar {
private String foo;
//getter setters
}
In order to get the POJO corresponding to the http body.
Now, another third party API I am consuming only return HTTP 200 as status response.
I would like to emphasize, there is no HTTP body. It is not the empty JSON {}.
Hence, I am a bit lost, and do not know what to put here. Especially with the goal of avoiding the mono empty.
public Mono<WhatToPutHerePlease> sendRequest(SomeRequest someRequest) {
return webClient.mutate()
.baseUrl("https://third-party-rest-api.com:443")
.build()
.post()
.uri(/someroute-with-no-http-body-response)
.body(BodyInserters.fromValue(someRequest))
.retrieve()
.bodyToMono(WhatToPutHerePlease.class);
}
Any help please?
Thank you
Hence, I am a bit lost, and do not know what to put here.
The response is empty, so there's nothing for your webclient to parse and return a value. The resulting Mono is thus always going to be empty, whatever generic type you use.
We have a special type that essentially says "this will always be empty" - Void (note the capital V.) So if you want to return an empty Mono, keeping the rest of the code the same, that's the type you should use.
Alternatively, if you don't want to return an empty publisher, then you might consider using .retrieve().toBodiLessEntity() instead of .retrieve().bodyToMono() - this will return a Mono<ResponseEntity<Void>>. The resulting body will obviously still be empty, but the response entity returned will enable you to extract information such as the response code & header information, should that be useful.
toBodylessEntity() seems to suit your needs:
It returns a Mono<ResponseBody<Void>>.
With a (void rest) controller like:
#RestController
#SpringBootApplication
public class Demo {
public static void main(String[] args) {
SpringApplication.run(Demo.class, args);
// ...
}
#GetMapping("/")
public void empty() {
}
}
and a:
public class ReactiveClient {
Mono<ResponseEntity<Void>> mono = WebClient.create("http://localhost:8080")
.get()
.retrieve()
.toBodilessEntity();
// blocking/synchronous
public ResponseEntity<Void> get() {
return mono.block();
}
}
We can:
ReactiveClient reactiveClient = new ReactiveClient();
System.out.println(reactiveClient.get()); // or something else

Send HTTP notifications with query params with Spring web client

I want to send http notifications with query params with Spring web client. I tried this:
WebClient client;
public Mono<Response> execute(String url) {
MultiValueMap map = new MultiValueMap<>();
map.add("some_key", "some_value");
return client.post().uri(builder -> builder
.host("http://www.test.com/notification")
.queryParams(map).build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(Response.class);
}
But I have several issues:
When I try to use MultiValueMap I get error Cannot instantiate the type MultiValueMap
Also how I can get the notification result? I don't want to send any payload or get any payload. I only want to get OK for response.
EDIT:
public Mono<String> execute(String url) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("some_key", "some_value");
return client.post().uri(builder -> builder
.queryParams(map).build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(String.class);
}
MultiValueMap
MultiValueMap is an interface from org.springframework.util
You are trying to instantiate it the following way:
MultiValueMap map = new MultiValueMap<>();
This won't work as you need to provide an implementation of the interface. If you really want to instantiate it this way then maybe try using one of the implementations of the MultiValueMap, like LinkedMultiValueMap.
OK for response
If you are only interested in the result (i.e. the status code) of the request, you could try to do the following:
client.post().uri(builder -> builder
.host("http://www.test.com/notification")
.queryParams(map).build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.exchange()
.map(ClientResponse::statusCode);
The last call to map(...) will return an instance of Mono<HttpStatus>.
Hope this helps.

How create a java object from CXF Webclient response

I'm writing a wrapper REST API (say API X) for an available REST API (say API Y) written in Apache CXF. For the wrapper I'm using CXF Webclient. This is how I call Y from X.
#GET
#Path("{userName}")
public Response getUser(#PathParam("userName") String userName) {
try {
WebClient client
= WebClient.create("https://localhost:8080/um/services/um");
Response response = client.path("users/" + userName)
.accept(MediaType.APPLICATION_JSON)
.get();
User user = (User) response.getEntity();
return Response.ok(user).build();
} catch (Exception e) {
return handleResponse(ResponseStatus.FAILED, e);
}
}
Here, User class is copied from Y to X because I can't use Y as a dependency for X. The only difference is the package name. Now when I send a request, I get a class cast exception at User user = (User) response.getEntity();.
java.lang.ClassCastException: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream cannot be cast to org.comp.rest.api.bean.User
May be that's becuase of the class package name is different?
Can someone please help me to get the response to User object?
Looks like your response is in JSON format, that right? You need to convert the stream of JSON bytes in the response to a Java Class. You are trying to cast the Stream Class to your User Class which obviously won't work. You need to parse the JSON from the data stream and then deserialize the JSON into your User Class. There are libraries that can help including Jackson and GSON
This guy has a simple example using Jackson ObjectMapper class - the ObjectMapper class has a readValue method that includes an InputStream parameter.
Jackson provider is a solution:
List<Object> providers = new ArrayList<Object>();
providers.add(new JacksonJaxbJsonProvider());
WebClient client = WebClient.create("https://localhost:8080/um/services/um", providers);
User user = client.get(User.class);
Don't need to do anything additional.
If it is a GET method
TypeOfObject response = client.path("users/" + userName)
.accept(MediaType.APPLICATION_JSON)
.get(TypeOfObject.class);
If it is a POST method
TypeOfObject response = client.path("users/" + userName)
.accept(MediaType.APPLICATION_JSON)
.post(instatanceOfTypeOfObject, TypeOfObject.class);

How to send JSON data in request body suing apache CXF webclient?

I am using apache cxf webclient to consume a service written in .NET
sample JSON to be sent in request body to a web service
{
"Conditions":
[
{
"Field":"TextBody",
"Comparer":"ContainsAny",
"Values":["stocks","retire"],
"Proximity":0
},
{
"Field":"SentAt",
"Comparer":"LessThan",
"Values":["1331769600"],
"Proximity":0
},
],
"Operator":"And",
"ExpireResultIn":3600
}
Is there any way if I want to submit data from both form and in Json body in one request ?
webclient API apache CXF -
web client API doc
WebClient client = WebClient.create("http://mylocalhost.com:8989/CXFTest/cxfws/rest/restservice/json");
client.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
After this which method and how to use ?
client.form(...form object )
client.post(...JSON string )
They have not shared Object of "Conditions" in JSON which I can annotate and pass to post method of client
I got answer here
Need to set JSON provider in my case it was jackson
List<Object> providers = new ArrayList<Object>();
providers.add( new JacksonJaxbJsonProvider() );
WebClient client = WebClient.create("http://localhost:8080/poc_restapi_cxf/api",
providers);
client = client.accept("application/json")
.type("application/json")
.path("/order")
.query("id", "1");
Order order = client.get(Order.class);
System.out.println("Order:" + order.getCustomerName());
There is a way to do this using annotations and suited my purpose:
#Post
#Path("mypath/json/whatever")
#Consumes({MediaType.APPLICATION_JSON_VALUE})
public Response postClient(#Context HttpHeaders headers, String input) {
//Here the String input will be equal to the supplied json.
//...
}

Categories

Resources