Spring WebFlux: How to route to a different handler function based on query parameters? - spring

I am writing a person API using Spring WebFlux functional programming, how to route to different handler functions based on the query param names?
#Bean
public RouterFunction<ServerResponse> route(PersonHandler personHandler) {
return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
.andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
.andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry)
// .andRoute(GET("/people?name={name}").and(accept(APPLICATION_JSON)), personHandler::searchByName)
// .andRoute(GET("/people?age={age}").and(accept(APPLICATION_JSON)), personHandler::searchByAge)
// I am expecting to do something like this
;
}
Or do I need to handle it in the handler function?
like
public Mono<ServerResponse> searchPeople(ServerRequest serverRequest) {
final Optional<String> name = serverRequest.queryParam("name");
final Optional<String> age = serverRequest.queryParam("age");
Flux<People> result;
if(name.isPresent()){
result = name.map(peopleRepository::searchByName)
.orElseThrow();
} else if(age.isPresent()){
result = name.map(peopleRepository::searchByage)
.orElseThrow();
}
return ok().contentType(MediaType.APPLICATION_JSON).body(result, People.class);
}
What is the best way to do it?
Thanks

You can create your own RequestPredicate and use the existing infrastructure (by plugging it into a and()):
public static RequestPredicate hasQueryParam(String name) {
return RequestPredicates.queryParam(name, p -> StringUtils.hasText(p));
}

Related

Setting [BindNever] during the action execution filter flow

Does anyone know how I can mark an argument on ActionDescriptor.Parameters to behave in a similar way the [BindNever] is behaving?
I want to always exclude a specific argument from a specific type without keep decorating it on the Controller.
Essentially I would like to be able to add my injected to my functions somehow how similar to the way its done with CancellationToken
public class TestController : ControllerBase
{
[HttpGet(Name = "Get")]
public IActionResult Get([BindNever] IInjectedInterface injected)
{
//Injected can be used in this method
return Ok();
}
[HttpPost(Name = "Post")]
public IActionResult Post([BindNever] IInjectedInterface injected, FormModel formModel)
{
//Injected doesn't work here. There is an error that
/*System.InvalidOperationException: 'Action 'WebApplication3.Controllers.TestController.Post (WebApplication3)'
has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body.
Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route,
and 'FromBodyAttribute' for parameters to be bound from body:
IInjectedInterface injected
FormModel formModel'
*/
return Ok();
}
}
public class ActionExecutionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var injectedParam = context.ActionDescriptor.Parameters.SingleOrDefault(x => x.ParameterType == typeof(IInjectedInterface));
if (injectedParam != null)
{
context.ActionArguments[injectedParam.Name] = new Injected(99);
}
await next.Invoke();
}
private class Injected : IInjectedInterface
{
public Injected(int someData)
{
SomeData = someData;
}
public int SomeData { get; }
}
}
I was able to solve it. Apparently you need to add the following lines on your program.cs to avoid the model binder related errors.
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(IInjectedInterface)));
options.ModelMetadataDetailsProviders.Add(
new BindingSourceMetadataProvider(typeof(IInjectedInterface), BindingSource.Special));

Spring HATEOAS RepresentationModelAssembler toCollectionModel()

I'm creating Spring Boot HATEOAS REST application. Code below shows how am I adding links, while GET request is send for specific Employee. I'm using RepresentationModelAssembler toModel function. There's also toCollectionModel function to Override, which I would like to use to convert List<Employees> to CollectionModel. -> This will be returned in /Employees/all endpoint.
And I dunno how to do that. So what I need is to pass List<Employees>, then all list elements needs to be processed by toModel functions, and then, like in toModel function I need possibility to add more links to it -> links to entire new collection (not individual items).
Looking forward for your answers!
#Component
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {
#Override
public EntityModel<Employee> toModel(Employee employee) {
EntityModel<Employee> employeeEntityModel = EntityModel.of(employee);
Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
employeeEntityModel.add(selfLink);
return employeeEntityModel;
}
#Override
public CollectionModel<EntityModel<Employee>> toCollectionModel(Iterable<? extends Employee> entities) {
?? ?? ??
}
}
You can use something like this:
#GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
List<Customer> allCustomers = customerService.allCustomers();
for (Customer customer : allCustomers) {
String customerId = customer.getCustomerId();
Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
customer.add(selfLink);
if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
Link ordersLink = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withRel("allOrders");
customer.add(ordersLink);
}
}
Link link = linkTo(CustomerController.class).withSelfRel();
CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
return result;
}
Visit https://www.baeldung.com/spring-hateoas-tutorial#springhateoasinaction for detailed explanation

Running Tasks in different thread in Spring Webflux Annotated controller

I have a spring Webflux Annotated controller as below,
#RestController
public class TestBlockingController {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return getStringMono();
}
private Mono<String> getStringMono() {
Integer blockSeconds = 5;
String type = new String();
try {
if (blockSeconds % 2 == 0) {
Thread.sleep(blockSeconds * 1000);
type = "EVEN";
} else {
Thread.sleep(blockSeconds * 1000);
type = "ODD";
}
} catch (Exception e) {
log.info("Got Exception");
}
log.info("Type of block-seconds: " + blockSeconds);
return Mono.just(type);
}
}
How do I make getStringMono run in a different thread than Netty server threads. The problem I am facing is that as I am running in server thread I am getting basically less throughput (2 requests per second). How do I go about making running getStringMono in a separate thread.
You can use subscribeOn operator to delegate the task to a different threadpool:
Mono.defer(() -> getStringMono()).subscribeOn(Schedulers.elastic());
Although, you have to note that this type of blocking should be avoided in a reactive application at any cost. If possible, use a client which supports non-blocking IO and returns a promise type (Mono, CompletableFuture, etc.). If you just want to have an artificial delay, then use Mono.delay instead.
You can use Mono.defer() method.
The method signature is as:
public static <T> Mono<T> defer(Supplier<? extends Mono<? extends T>> supplier)
Your Rest API should look like this.
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return Mono.defer(() -> getStringMono());
}
The defer operator is there to make this source lazy, re-evaluating the content of the lambda each time there is a new subscriber. This will increase your API throughput.
Here you can view the detailed analysis.

How to specify response type in ASP.NET Core middleware

My controllers return unified RequestResult:
public Task<RequestResult> SomeAction()
{
...
return new RequestResult(RequestResultType.NotFound);
}
public class RequestResult
{
public RequestResultType Type { get;set; }
... //actual data
}
public enum RequestResultType
{
Success = 1,
NotFound = 2
}
So basically RequestResult combines actual Action data and error type (if it happened). Now I need to specify Response Type at some point in case if Action returned Error. My best guess here is to use Middleware:
public class ResponseTypeMiddleware
{
private readonly RequestDelegate next;
public ResponseTypeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
var response = context.Response.Body; //how to access object?
}
}
but I can't figure out what to do with it. What I'd perfectly like to do is to check if response is of type RequestResult, then specify ResponseType equal BadRequest. But I don't see how I can do it here as what I have is just a stream. May be I can hijack into pipeline earlier, before result was serialized (Controller?).
P. S. The reason why I don't use Controller.BadRequest directly in Action is that my Action's logic is implemented via CQRS command/query handlers, so I don't have direct access to Controller.
As you are going to process controller's action result (MVC), the best way is to use ActionFilter or ResultFilter here, instead of Middleware. Filters in ASP.NET Core are a part of MVC and so know about controllers, actions and so on. Middleware is a more common conception - it is an additional chain in application request-response pipeline.
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
// get or set controller action result here
var result = context.Result as RequestResult;
}
}

Add camel route at runtime in Java

How can I add a camel route at run-time in Java? I have found a Grails example but I have implement it in Java.
My applicationContext.xml already has some predefined static routes and I want to add some dynamic routes to it at run time.
Is it possible?
Because the only way to include dynamic route is to write the route.xml and then load the route definition to context. How will it work on existing static routes?
Route at runtime
you can simply call a few different APIs on the CamelContext to add routes...something like this
context.addRoutes(new MyDynamcRouteBuilder(context, "direct:foo", "mock:foo"));
....
private static final class MyDynamcRouteBuilder extends RouteBuilder {
private final String from;
private final String to;
private MyDynamcRouteBuilder(CamelContext context, String from, String to) {
super(context);
this.from = from;
this.to = to;
}
#Override
public void configure() throws Exception {
from(from).to(to);
}
}
see this unit test for the complete example...
https://svn.apache.org/repos/asf/camel/trunk/camel-core/src/test/java/org/apache/camel/builder/AddRoutesAtRuntimeTest.java
#Himanshu,
Please take a look at dynamicroute options (in other words routing slip) that may help you dynamically route to different 'destinations' based on certain condition.
Check the dynamic router help link in camel site;
http://camel.apache.org/dynamic-router.html
from("direct:start")
// use a bean as the dynamic router
.dynamicRouter(method(DynamicRouterTest.class, "slip"));
And within the slip method;
/**
* Use this method to compute dynamic where we should route next.
*
* #param body the message body
* #return endpoints to go, or <tt>null</tt> to indicate the end
*/
public String slip(String body) {
bodies.add(body);
invoked++;
if (invoked == 1) {
return "mock:a";
} else if (invoked == 2) {
return "mock:b,mock:c";
} else if (invoked == 3) {
return "direct:foo";
} else if (invoked == 4) {
return "mock:result";
}
// no more so return null
return null;
}
Hope it helps...
Thanks.
One such solution could be:
Define route:
private RouteDefinition buildRouteDefinition() {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.from(XX).to(ZZ); // define any route you want
return routeDefinition;
}
Get Model Context and create route:
CamelContext context = getContext();
ModelCamelContext modelContext = context.adapt(ModelCamelContext.class);
modelContext.addRouteDefinition(routeDefinition);
There are more way of getting camel context. To name few:
In processor, you can use exchange.getContext()
Through RouteBuilder reference, you can use routeBuilder.getContext()

Resources