a class UploadService receives a FilePart, representing the content of the JSON below, from a REST API.
The target is to return a Mono<String> from this FilePart to build a JSONObject.
This is the posted JSON:
{
"machineId" : "7",
"printJobId" : "123",
"timeStampStartPrintJob" : "10:23:15,253",
"timeStampEndPrintJob" : "12:50:16,577",
"optionalMetadata" : {}
}
This is my code:
public class UploadService{
public Mono<String> uploadSensorData(FilePart metaDataFile) {
/*create + print a JSONObject*/
JSONObject result = new JSONObject(metaDataFileContentToString(metaDataFile).toString().trim().charAt(0));
System.out.println(result);
}
/*convert FilePart to String*/
private Mono<String> metaDataFileContentToString(FilePart metaDataFile) {
return metaDataFile.content()
.map(buffer -> buffer.toString(StandardCharsets.UTF_8))
.collectList()
.map(list -> String.join("", list));
}
return null;
}
Issue: I'm not sure if I'm on the right track with my method metaDataFileContentToString. By now the output only shows the first and last curly bracket of the JSON: {}
Is there another way to get a proper Mono<String> from the bytes of the Filepart? Or is maybe the .toString() method by creating the JSONObject the problem?
Many thanks for your ideas in advance!
The target is to return a Mono<String> from this FilePart to build a JSONObject
There's no need to do this - modern JSON libraries like Jackson have built in methods to asynchronously process a Flux<DataBuffer> straight into the type that's required, skipping the String.
So assuming you have a class PrintJobInfo that represents your data structure, you can just do something like:
Mono<PrintJobInfo> mono = new Jackson2JsonDecoder()
.decodeToMono(fp.content(), ResolvableType.forClass(PrintJobInfo.class), null, null)
.cast(PrintJobInfo.class);
In real-world use you'd probably want to expose your Jackson2JsonDecoder as a bean rather than creating a new one each time. Note it also has another constructor that can take an ObjectMapper if you need any custom configuration.
What string do you want?
If you want a print of the json then use ObjectMapper::writeValueAsString or similar, or yes, toString.
To get a Mono then use Mono::just
However, you should not creat a Mono in your methods but rather you should be part of an existing flow. This means you should be getting a Mono and returning a Mono and your function is simply mapping the data from one type to another.
public Mono<String> uploadSensorData(Mono<FilePart> metaDataFile) {
return metaDataFile.map(FilePart::toString);
}
Related
I'm trying to reactively fetch data from external API using two methods from some Service class.
I'm new to reactive and Spring in general, so it could be a very obvious mistake but I just can't find it
These are the two methods:
public Mono<SomeClass> get(int value) {
return webClient.get()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.CONFLICT::equals, clientResponse -> {
return Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode()));
})
.onRawStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> {
return requestGeneration(value)
.flatMap(res -> Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode())));
})
.bodyToMono(SomeClass.class)
.retryWhen(Retry.backoff(5, Duration.ofSeconds(8))
.filter(throwable -> throwable instanceof SomeException1));
}
private Mono<Void> requestGeneration(int value) {
return webClient.post()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.BAD_REQUEST::equals, clientResponse -> {
return Mono.error(new SomeException2("Wrong value", clientResponse.rawStatusCode()));
})
.bodyToMono(Void.class);
}
Baasically what I'm trying to achieve is:
first GET from http://api.examplepage.com/{value}
if that API returns HTTP404 it means I need to first call POST to the same URL, because the data is not yet generated
the second function does the POST call and returns Mono<Void> because it is just HTTP200 or HTTP400 on bad generation seed (i don't need to process the response)
first function (GET call) could also return HTTP429 which means the data is generating right now, so I need to call again after some time period (5-300 seconds) and check if data has been generated already
then after some time it results in HTTP200 with generated data which I want to map to SomeClass and then return mapped data in controller below
#PostMapping("/follow/{value}")
public Mono<ResponseEntity<?>> someFunction(#PathVariable int value) {
return Mono.just(ResponseEntity.ok(service.get(value)));
}
all the code I posted is very simplified to the issues I'm struggling with and doesn't contain some things I think are not important in this question
and now the actual question:
it doesn't actually make the call? i really don't know what is happening
program doesn't enter onRawStatus, even if i change it to onStatus 2xx or whatever other httpstatus and log inside i see nothing as if it doesn't even enter the chains
when i manually call with postman it seems like the program calls have never been made because the GET call returns 404 (the program didn't request to generate data)
the controller only returns "scanAvailable": true, when i expect it to return mapped SomeClass json
// edit
i changed the code to be a full chain as suggested and it didn't solve the problem. all the status are still unreachable (code inside any onStatus nor onRawStatus never executes)
I am trying to implement the BodyExtractor interface get the body from Mono< ClientResponse> as an Object instead of it in Mono.
I could not find any example of BodyExtractor implementation. I am wondering is this a good idea to implement it or is there any other way to get the body as an object.
Below is the code line that I currently have
public Mono<ResponseEntity<Mono<JsonNode>>> processUnmappedApiRequest(ServerHttpRequest request, JsonNode body) {
RequestData reqData = this.prepareReqMetadata(request, body);
Mono<ClientResponse> response = commonConnector.getApiResponse(reqData);
return response.map(respData -> {
int latestVersion = respData.headers().header("version").size() == 0 ? getLatestVersion(request) :
Integer.parseInt(respData.headers().header("version").get(0));
List converterList;
if((converterList = converterSequenceProvider.getConverterList(reqData.getRequestPath(), latestVersion, reqData.getVersion())) != null){
return ResponseEntity.ok().body(respData.bodyToMono(JsonNode.class).map(respBody -> convertToDesiredVersion(converterList, respBody)));
}
return ResponseEntity.ok().body(respData.bodyToMono(JsonNode.class));
});
}
In this method my return type is Mono< ResponseEntity< Mono< JsonNode>>> and I am trying to convert it to Mono< ResponseEntity< JsonNode>> because my team is not agreeing with Mono inside a Mono.
So the main point here is I don't want to use bodyToMono method and I am not sure how to use body method.
Please help me out here.
If you are trying to return just a Mono object, you can use the flatMap method instead of map, so you can avoid something like Mono<Mono<X>> and get just Mono<X>.
map
Transform the item emitted by this Mono by applying a synchronous
function to it.
flatMap
Transform the item emitted by this Mono asynchronously, returning the
value emitted by another Mono (possibly changing the value type).
Also, there is a method on ServerResponse.BodyBuilder syncBody which can take normal body and return it in Mono. Map function's argument is already a non wrapped object, so you can do something like:
JsonNode jsonNode=transform(clientResponse);
return ResponseEntity.ok().syncbody(jsonNode);
I am trying to build an application A (like an adaptor) that will:
1) Receive POST requests with some key (JSON format)
2) It should modify that key somehow and create POST request to another system B.
3) Application A should parse the response from application B and modify that response.
4) After that my application A should answer to the initial POST request.
#RestController
#RequestMapping("/A")
public class Controller {
#ResponseStatus(HttpStatus.OK)
#PostMapping(value = "B", consumes = APPLICATION_JSON_VALUE)
// to return nested Flux is a bad idea here
private Flux<Flux<Map<String, ResultClass>>> testUpdAcc(#RequestBody Flux<Map<String, SomeClass>> keys) {
return someMethod(keys);
}
// the problem comes here when I will get Flux<Flux<T>> in the return
public Flux<Flux<Map<String, ResultClass>>> someMethod(Flux<Map<String, SomeClass>> keysFlux) {
return keysFlux.map(keysMap -> {
// do something with keys and create URL
// also will batch keys here
<...>
// for each batch of keys:
WebClient.create(hostAndPort)
.method(HttpMethod.POST)
.uri(url)
.body(BodyInserters.fromObject(body))
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(schema) // response will be parsed into some schema here
.retryWhen (// will make a retry mechanism here)
// ===== will join all Mono batches into single Flux
Flux.concat(...);
}
);
}
}
Of course this can be fixed by not reading keysFlux as Flux and read that as Map. But that should make everything less reactive, no? :)
#ResponseStatus(HttpStatus.OK)
#PostMapping(value = "B", consumes = APPLICATION_JSON_VALUE)
// to return nested Flux is a bad idea here
private Flux<Map<String, ResultClass>> testUpdAcc(#RequestBody Map<String, SomeClass> keys) {
return someMethod(keys);
}
Also I have tried to use block()/blockFirst() in the last moment before returning the request, but I have got an error:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor...
Thank you for your ideas!
Forget about my question - we can easily use "flatMap" instead of "map".
That will solve a problem with Flux inside Flux.
Try to zip all the flux like this
Flux.zip(flux1,flux2)
It will create Tuple2 so that you can do flatMap
Thanks,
Vimalesh
The application i'm writing performs an initial API call with Retrofit which returns a URL. That response is then .flatMap'd into another API call depending on the text contained in the URL. However, the two secondary API calls are defined to return different response models.
To make things clearer, here is some code:
APIService service = retrofit.create(APIService.class);
service.getURL() // returns response model containing a URL.
.flatMap(new Function<GetURLResponse, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(GetURLResponse getURLResponse) throws Exception {
// Determine whether the returned url is for "first.com".
if (getURLResponse.url.contains("first.com")) {
return service.first(getURLResponse.url);
}
// Otherwise, the URL is not for "first.com", so use our other service method.
return service.second(getURLResponse.url);
}
})
Here are the interface definitions for service.first() and service.second():
#GET
Observable<retrofit2.Response<ResponseBody>> first(#Url String url);
#GET
Observable<SecondModel> second(#Url String url);
How can I better handle these two different possible types (retrofit2.Response<ResponseBody> and SecondModel) for the rest of the stream? Eg. If the initial URL contains first.com then the service.first() API call should fire, and operators down the stream should received a retrofit2.Response<ResponseBody>. Conversely, if the initial URL does not contain first.com, the service.second() API call should fire and operators down the stream should receive a SecondModel.
The easiest way would be to have both your model classes implement an interface and return that interface, alternatively the models could both extend an abstract class to achieve the same effect. You would then do an instanceOf check to see which model it is and continue with your preferred transformations.
That having said you mentioning downstream operators, makes me think that this would cause an annoying amount of checks. So what I would do is split the stream using the publish operator, and then apply your further transformations to each sub-stream. Finally you should merge the two streams back together and return a single model encompassing both models.
Below a code example to get you started.
Observable<Integer> fooObservableSecondaryRequest(String foo) {
return Observable.just(1);
}
Observable<Integer> booObservableSecondaryRequest(String boo) {
return Observable.just(2);
}
Observable<String> stringObservable = Observable.just("foo", "boo");
stringObservable.publish(shared -> Observable.merge(
shared.filter(a -> a.equals("foo"))
.flatMap(fooString -> fooObservableSecondaryRequest(fooString))
.map(num -> num * 2),
shared.filter(a -> a.equals("boo"))
.flatMap(booString -> booObservableSecondaryRequest(booString))
.map(num -> num * 10)
)).subscribe(result -> System.out.println(result)); // will print 2 and 20
I have a method which accepts Mono as a param.
All I want is to get the actual String from it. Googled but didn't find answer except calling block() over Mono object but it will make a blocking call so want to avoid using block(). Please suggest other way if possible.
The reason why I need this String is because inside this method I need to call another method say print() with the actual String value.
I understand this is easy but I am new to reactive programming.
Code:
public String getValue(Mono<String> monoString) {
// How to get actual String from param monoString
// and call print(String) method
}
public void print(String str) {
System.out.println(str);
}
Getting a String from a Mono<String> without a blocking call isn't easy, it's impossible. By definition. If the String isn't available yet (which Mono<String> allows), you can't get it except by waiting until it comes in and that's exactly what blocking is.
Instead of "getting a String" you subscribe to the Mono and the Subscriber you pass will get the String when it becomes available (maybe immediately). E.g.
myMono.subscribe(
value -> System.out.println(value),
error -> error.printStackTrace(),
() -> System.out.println("completed without a value")
)
will print the value or error produced by myMono (type of value is String, type of error is Throwable). At https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html you can see other variants of subscribe too.
According to the doc you can do:
String getValue(Mono<String> mono) {
return mono.block();
}
be aware of the blocking call
Finally what worked for me is calling flatMap method like below:
public void getValue(Mono<String> monoString)
{
monoString.flatMap(this::print);
}
What worked for me was the following:
monoString.subscribe(this::print);
Simplest answer is:
String returnVal = mono.block();
This should work
String str = monoString.toProcessor().block();
Better
monoUser.map(User::getId)