I have the below controller
#RestController
#RequestMapping("/view")
public class ViewController {
#GetMapping(value = "/{channelId}/**")
public Mono<ResponseEntity<ViewResponse>> viewObject(#PathVariable(value = "channelId") String channelId) {
return redisController.getChannelData(channelInfoset, channelId).map(response -> {
Mono<ViewResponse> processOutput = processViewUrl(channelId); // returns object of Mono.just
return new ResponseEntity<>(processOutput, responseHeaders, HttpStatus.OK);
}}
The method which returns the mono object
private Mono<ViewResponse> processViewUrl(String channelId){
return Mono.just(new ViewResponse("val","temp",false));
}
This gives me an error saying
Incompatible types. Found: 'reactor.core.publisher.Mono<java.lang.Object>', required: 'reactor.core.publisher.Mono<org.springframework.http.ResponseEntity<com.api.model.ViewResponse>>'
What is wrong over here?
processOutput is a Mono<ViewResponse>, not a ViewResponse. To obtain a Mono<ResponseEntity<ViewResponse>>, you should map processOutput to a ResponseEntity:
return redisController.getChannelData(channelInfoset, channelId)
.map(response -> {
Mono<ViewResponse> processOutput = processViewUrl(channelId);
return processOutput.map(value -> new ResponseEntity(value, response.getHeaders(), HttpStatus.OK));
}}
Note that if there's no constraint over your processViewUrl method signature (API compliance, potential other async/complex implementation, etc.), I would recommend to change it to return directly a ViewResponse instead of a mono. Mono.just is not necessary here.
In general cases, Mono.just is required only when you want to provide a value to an operation that requires a publisher as input.
If you return directly a ViewResponse instead of a Mono<ViewResponse>, your initial code should work.
Related
I am new to reactive programming concepts and trying to build one service that sends requests to a two backend service in parallel and combine those results.
Those two backend service has a different response structure and i have created a mapper method to convert all that into a common Response structure.
This is what i have right now and it is working when both the services return results.
public Mono<List<Response>> getRecords(String input){
List<Response> response = new ArrayList<>();
Mono<FirstApiResponse> gResp = this.firstWebClient.get().uri(uriBuilder -> uriBuilder
.path("/")
.queryParam("q", input)
.build()).retrieve()
.bodyToMono(FirstApiResponse.class).log()
.timeout(Duration.ofSeconds(50L));
Mono<SecondApiResponse> iResp = this.secondWebClient.get().uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("term", input)
.build()).retrieve()
.bodyToMono(SecondApiResponse.class).log().timeout(Duration.ofSeconds(50L));
return Mono.zip(firstResp,secResp).map(objects ->{
if(firstResp != null)
response.addAll(Mapper.convert(objects.getT1()));
if(secResp != null);
response.addAll(Mapper.convert(objects.getT2()));
return response;
});
}
public List<Response> convert(FirstApiResponse resp){
////
Mapping to Response object
////
return response;
}
public List<Response> convert(SecondApiResponse resp){
////
Mapping to Response object
////
return response;
}
I don't know if this is the right way to do it. Moreover, i want to make it in such a way that if there is any errors from any of this service, then it should still return the results from the other service. Right now it throws the exception and I am not able to figure out how to handle it properly
How to handle these errors in a proper way ?
This is a pretty valid scenario and there are many ways to handle it. One crude way would be to use onErrorReturn a new Model which you can handle. It could be either an empty response or a wrapper around your model whichever seems fit for your scenario.
Mono<Wrapper<FirstApiResponse>> gResp = this.firstWebClient.get().uri(uriBuilder -> uriBuilder
.path("/")
.queryParam("q", input)
.build()).retrieve()
.bodyToMono(FirstApiResponse.class).log()
.map( response -> new Wrapper().withResponse(response))
.timeout(Duration.ofSeconds(50L))
.doOnError(throwable -> logger.error("Failed", throwable))
.onErrorReturn(new Wrapper().withError( YourDefaultErrorReponse(...));
Mono<SecondApiResponse> iResp = this.secondWebClient.get().uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("term", input)
.build())
.retrieve()
.bodyToMono(SecondApiResponse.class).log()
.map( response -> new Wrapper().withResponse(response))
.timeout(Duration.ofSeconds(50L))
..doOnError(throwable -> logger.error("Failed", throwable))
.onErrorReturn(new Wrapper().withError( YourDefaultErrorReponse(...))
Again there are ways to return a default response. A simple one would be to use something like a wrapper
public final class Wrapper<T> {
private T response ;
private Error error;
public Wrapper<T> withResponse ( T response ){
this.response = response;
return this;
}
public Wrapper<T> withError( Error error) {
this.error = error;
return this;
}
public Boolean hasError(){
return error != null ;
}
public T getResponse(){
return response;
}
}
How do I handle error states inside a controller?
For example: My controller which accepts two headers. If both the headers are null then it's a problem so return response of type ErrorResponse else if all ok then return response of type customer.
However, there is a problem as we defined the handler method to return ResponseEntity<Customer>.
My controller:
#PostMapping("/generate/send")
fun handleRequest( #RequestHeader header1: String,
#RequestHeader header2: String): ResponseEntity<Customer> {
if(header1 == null && header2== null) { // ERROR if both header null
return ResponseEntity(ErrorResponse(""), HttpStatus.BAD_REQUEST )
}
return ResponseEntity(Customer(), HttpStatus.OK )
}
how do refactor my code to prevent this or handle this type of situation where I have to return a different type because of an error?
You can use ResponseEntity<?> to return either Customer or ErrorResponse like this.
#PostMapping("/generate/send")
fun handleRequest( #RequestHeader header1: String,
#RequestHeader header2: String): ResponseEntity<?> {
// ...
}
I want to use ARCore with Xamarin and I stuck with error handling in CompletableFuture objects for example ModelRenderable.Builder(). Documentation says that I should use Exceptionally() function but I can't find any samples how to do it in C#.
In Xamarin Exceptionally() function require to use something that implements Java.Util.Function.IFunctions interface but my very simple class throws "Specified cast is not valid" error on runtime. Does anyone know how to use Exceptionally() function?
//here is java code how this should be implemented
ModelRenderable.builder()
.setSource(this, Uri.parse("..."))
.build()
.thenAccept(renderable -> andyRenderable2 = renderable)
.exceptionally(
throwable -> {
//show error
return null;
});
//here is my code in C#
var builder = new ModelRenderable.Builder();
builder.SetSource(this, Uri.parse("..."));
builder.Build()
.ThenAccept(new RenderableConsumer(renderable => AddNodeToScene(renderable)))
.Exceptionally(new Foo(() => { throw new Exception("error"); }));
//here is my very simple implementation of IFunction interface
internal class Foo : Java.Lang.Throwable, IFunction
{
public Foo(Action action) { }
public Java.Lang.Object Apply(Java.Lang.Object t)
{
return t;
}
}
EDITED
//alternative approach to ThenAccept() but in this case Exception is also not thrown
builder.Build().GetAsync()
.ContinueWith(renderable => AddNodeToScene((Renderable)renderable.Result), new System.Threading.CancellationToken(), TaskContinuationOptions.NotOnFaulted, TaskScheduler.FromCurrentSynchronizationContext())
.ContinueWith(error => new Exception("Error!"), TaskContinuationOptions.OnlyOnFaulted);
I have a simple CRUD controller. When updating I want merge the updated model with the model that has to be updated, this is what I have at the moment:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
// Update the server here.
// server.setName(updatedServer.getName());
// server.setHost(updatedServer.getHost());
server.flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}
How can I edit the server variable before the save? When subscribing on the Mono it will be executed after the save.
I know this is a pretty simple question, but I just can't find a way to do it.
I always find the answer just after asking the question...
I just used another flatmap to edit the object:
#PutMapping("{id}")
public Mono<Server> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save);
}
Or when still returning success: true:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}
Below is the method I've got in my ApiController class.
The _fileContents dictionary is populated in another WebApi2 method BuildContent(params).
When the user makes an ajax call to the BuildContent(params) method, the method builds
the string end of the dictionary, which contains full HTML content including a table tag and passes back a the Guid, end of the dictionary. The javascript in turn does the following:
window.location = 'api/MyController/DownloadFile/' + guid;
ApiController static:
private static Dictionary<Guid, String> _fileContents = new Dictionary<Guid, String>();
ApiController method:
public IHttpActionResult DownloadFile(Guid guid)
{
try
{
if (_fileContents.ContainsKey(guid))
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var content = Encoding.ASCII.GetBytes(_fileContents[guid]);
using (var stream = new MemoryStream(content))
{
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "MyFile.bin";
}
return Ok(result);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
return BadRequest();
}
The calling sequence works perfectly but the DownloadFile() method throws the following exception:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Type 'System.Net.Http.StreamContent' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute. See the Microsoft .NET Framework documentation for other supported types.
</ExceptionMessage>
<ExceptionType>
System.Runtime.Serialization.InvalidDataContractException
</ExceptionType>
Can anyone tell me what is going on and how to accomplish my goal of downloading a simple html file?