Spring Integration Java DSL: How to loop the paged Rest service? - spring

How to loop the paged Rest service with the Java DSL Http.outboundGatewaymethod?
The rest URL is for example
http://localhost:8080/people?page=3
and it returns for example
"content": [
{"name": "Mike",
"city": "MyCity"
},
{"name": "Peter",
"city": "MyCity"
},
...
]
"pageable": {
"sort": {
"sorted": false,
"unsorted": true
},
"pageSize": 20,
"pageNumber": 3,
"offset": 60,
"paged": true,
"unpaged": false
},
"last": false,
"totalElements": 250,
"totalPages": 13,
"first": false,
"sort": {
"sorted": false,
"unsorted": true
},
"number": 3,
"numberOfElements": 20,
"size": 20
}
where the variable totalPages tells the total pages amount.
So if the implementation
integrationFlowBuilder
.handle(Http
.outboundGateway("http://localhost:8080/people?page=3")
.httpMethod(HttpMethod.GET)
.expectedResponseType(String.class))
access one page, how to loop all the pages?

The easiest way to do this is like wrapping the call to this Http.outboundGateway() with the #MessagingGateway and provide a page number as an argument:
#MessagingGateway
public interface HttpPagingGateway {
#Gateway(requestChannel = "httpPagingGatewayChannel")
String getPage(int page);
}
Then you get a JSON as a result, where you can convert it into some domain model or just perform a JsonPathUtils.evaluate() (based on json-path) to get the value of the last attribute to be sure that you need to call that getPage() for the page++ or not.
The page argument is going to be a payload of the message to send and that can be used as an uriVariable:
.handle(Http
.outboundGateway("http://localhost:8080/people?page={page}")
.httpMethod(HttpMethod.GET)
.uriVariable("page", Message::getPayload)
.expectedResponseType(String.class))
Of course, we can do something similar with Spring Integration, but there are going to be involved filter, router and some other components.
UPDATE
First of all I would suggest you to create a domain model (some Java Bean), let's say PersonPageResult, to represent that JSON response and this type to the expectedResponseType(PersonPageResult.class) property of the Http.outboundGateway(). The RestTemplate together with the MappingJackson2HttpMessageConverter out-of-the-box will do the trick for you to return such an object as a reply for the downstream processing.
Then, as I said before, looping would be better done from some Java code, which you could wrap to the service activator call. For this purpose you should daclare a gateway like this:
public interface HttpPagingGateway {
PersonPageResult getPage(int page);
}
Pay attention: no annotations at all. The trick is done via IntegrationFlow:
#Bean
public IntegrationFlow httpGatewayFlow() {
return IntegrationFlows.from(HttpPagingGateway.class)
.handle(Http
.outboundGateway("http://localhost:8080/people?page={page}")
.httpMethod(HttpMethod.GET)
.uriVariable("page", Message::getPayload)
.expectedResponseType(PersonPageResult.class))
}
See IntegrationFlows.from(Class<?> aClass) JavaDocs.
Such a HttpPagingGateway can be injected into some service with hard looping logic:
int page = 1;
boolean last = false;
while(!last) {
PersonPageResult result = this.httpPagingGateway.getPage(page++);
last = result.getLast();
List<Person> persons = result.getPersons();
// Process persons
}
For processing those persons I would suggest to have separate IntegrationFlow, which may start from the gateway as well or you can just send a Message<List<Person>> to its input channel.
This way you will separate concerns about paging and processing and will have a simple loop logic in some POJO method.

Related

Springdocs: Specifying an explicit type for Paged responses

I'm working on a "global search" for my application.
Currently, I'm using hibernate-search to search for instances of multiple different objects and return them to the user.
The relevant code looks as follows:
Search.session(entityManager)
.search(ModelA.classs, ModelB.class)
.where(...)
.sort(...)
.fetch(skip, count);
Skip and count are calculated based on a Pageable and the result is used to create an instance of Page, which will be returned to the controller.
This works as I'd expect, however, the types generated by swagger-docs obviously doesn't know, what the type within the Page is, and therefore uses Object.
I'd like to expose the correct types, as I use them to generate the types for the frontend application.
I was able to set the type to an array, when overwriting the schema like this:
#ArraySchema(schema = #Schema(anyOf = {ModelA.class, ModelB.class}))
public Page<?> search(Pageable pageable) {
However, this just disregards the Page and also isn't correct.
The next thing I tried is extending the PageImpl, overwriting the getContent method, and specifying the same schema on this method, but this wasn't included in the output at all.
Next was implementing Page<T> myself (and later removing the implements reference to Page<T>) and specifying the same schema on getContent, iterator, and the field itself, but also to no effect.
How do I tell spring-docs, what the content of the resulting Page might be?
I stumbled upon this when trying to solve a similar problem
Inspired from this thread Springdoc with a generic return type i came up with the following solution, and it seems to apply to your case also. Code examples are in Kotlin.
I introduced a stub class that will just act as the Schema for the response:
private class PageModel(
#Schema(oneOf = [ModelA::class, ModelB::class]))
content: List<Object>
): PageImpl<Object>(content)
Then i annotated my Controller like this:
#Operation(
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = PageModel::class))]
)
]
)
fun getPage(pageable: Pageable): Page<Object>
This generated this api response:
"PageModel": {
"properties": {
"content": {
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/ModelA"
},
{
"$ref": "#/components/schemas/ModelB"
}
],
"type": "object"
},
"type": "array"
},
... -> more page stuff from spring's PageImpl<>
And in the "responses" section for the api call:
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PageModel"
}
}
},
"description": "OK"
}
All generated openapi doc is similar to the autogenerated json when returning a Page, it just rewrites the "content" array property to have a specific type.

Jackson derealization with SpringBoot : To get field names present in request along with respective field mapping

I have a requirement to throw different error in case of different scenarios like below, and there are many such fields not just 1.
e.g.
{
"id": 1,
"name": "nameWithSpecialChar$"
}
Here it should throw error for special character.
{
"id": 1,
"name": null
}
Here throw field null error.
{
"id": 1
}
Here throw field missing error.
Handling, 1st and 2nd scenario is easy, but for 3rd one, is there any way we can have a List of name of fields that were passed in input json at the time of serialization itself with Jackson?
One way, I am able to do it is via mapping request to JsonNode and then check if nodes are present for required fields and after that deserialize that JsonNode manually and then validate rest of the members as below.
public ResponseEntity myGetRequest(#RequestBody JsonNode requestJsonNode) {
if(!requestJsonNode.has("name")){
throw some error;
}
MyRequest request = ObjectMapper.convertValue(requestJsonNode, MyRequest .class);
validateIfFieldsAreInvalid(request);
But I do not like this approach, is there any other way of doing it?
You can define a JSON schema and validate your object against it. In your example, your schema may look like this:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"description": "The identifier",
"type": "integer"
},
"name": {
"description": "The item name",
"type": "string",
"pattern": "^[a-zA-Z]*$"
}
},
"required": [ "id", "name" ]
}
To validate your object, you could use the json-schema-validator library. This library is built on Jackson. Since you're using Spring Boot anyway, you already have Jackson imported.
The example code looks more or less like this:
String schema = "<define your schema here>";
String data = "<put your data here>";
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
ObjectMapper m = new ObjectMapper();
JsonSchema jsonSchema = factory.getJsonSchema(m.readTree(schema));
JsonNode json = m.readTree(data);
ProcessingReport report = jsonSchema.validate(json);
System.out.println(report);
The report includes detailed errors for different input cases. For example, with this input
{
"id": 1,
"name": "nameWithSpecialChar$"
}
this output is printed out
--- BEGIN MESSAGES ---
error: ECMA 262 regex "^[a-zA-Z]*$" does not match input string "nameWithSpecialChar$"
level: "error"
schema: {"loadingURI":"#","pointer":"/properties/name"}
instance: {"pointer":"/name"}
domain: "validation"
keyword: "pattern"
regex: "^[a-zA-Z]*$"
string: "nameWithSpecialChar$"
--- END MESSAGES ---
Or instead of just printing out the report, you can loop through all errors and have your specific logic
for (ProcessingMessage message : report) {
// Add your logic here
}
You could check the example code to gain more information about how to use the library.

how to send json data stream to multiple topics in kafka based on input fields

I have to consume json data coming to kafka stream and send to diffrent topics (distinct combination of app id and entity) for further consumption.
topic names :
app1.entity1
app1.entity2
app2.entity1
app2.entity2
Json Data
[
{
"appId": "app1",
"entity": "entity1",
"extractType": "txn",
"status": "success",
"fileId": "21151235"
},
{
"appId": "app1",
"entity": "entity2",
"extractType": "txn",
"status": "fail",
"fileId": "2134234123"
},
{
"appId": "app2",
"entity": "entity3",
"extractType": "payment",
"status": "success",
"fileId": "2312de23e"
},
{
"appId": "app2",
"entity": "entity3",
"extractType": "txn",
"status": "fail",
"fileId": "asxs3434"
}
]
TestInput.java
private String appId;
private String entity ;
private String extractType;
private String status;
private String fileId;
setter/gtter
SpringBootConfig.java
#Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration kStreamsConfigs(KafkaProperties kafkaProperties) {
Map<String, Object> config = new HashMap<>();
config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
config.put(StreamsConfig.APPLICATION_ID_CONFIG, kafkaProperties.getClientId());
config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, new JsonSerde<>(TestInput.class).getClass());
config.put(JsonDeserializer.DEFAULT_KEY_TYPE, String.class);
config.put(JsonDeserializer.DEFAULT_VALUE_TYPE, TestInput.class);
return new KafkaStreamsConfiguration(config);
}
#Bean
public KStream<String, TestInput> kStream(StreamsBuilder kStreamBuilder) {
KStream<String, TestInput> stream = kStreamBuilder.stream(inputTopic);
// how to form key , group records and send to different topics
return stream;
}
I searched a lot but didnt find anything near which publishes data to topics dynamically. Please help experts
Use stream.branch()
See https://www.confluent.io/blog/putting-events-in-their-place-with-dynamic-routing/
Next, let’s modify the requirement. Instead of processing all events in the stream, each microservice should take action only on a subset of relevant events. One way to handle this requirement is to have a microservice that subscribes to the original stream with all the events, examines each record and then takes action only on the events it cares about while discarding the rest. However, depending on the application, this may be undesirable or resource intensive.
A cleaner way is to provide the service with a separate stream that contains only the relevant subset of events that the microservice cares about. To achieve this, a streaming application can branch the original event stream into different substreams using the method KStream#branch(). This results in new Kafka topics, so then the microservice can subscribe to one of the branched streams directly.
...

How to configure Spring boot pagination starting from page 1, not 0

boot(1.4.0) "Pageable" for pagination.It works fine without any issue.But by default the page value starts from "0" but in the front-end the page value starts from "1". So is there any standard approach to increment value instead of manually increment the page number inside the code?
public Page<Device> find(DeviceFindCommand deviceFindCommand, Pageable pageable){
//page = 0 //Actual is 0, Expected increment by 1.
}
Any help should be appreciable.
After implementing Alan answers having the following issues,
1) Still i am able to access zero page which returns the first page(I don't know this is issue or not but i want to get a better clarity).
http://localhost:8180/api/v1/books/?page=3&size=2
Response
{
"content": [
{
"id": "57da9eadbee83fb037a66029",
.
.
.
}{
.
.
.
}
],
"last": false,
"totalElements": 5,
"totalPages": 3,
"size": 2,
"number": 2, //strange always getting 1 less than page number.
"sort": null,
"first": true,
"numberOfElements": 2
}
2) "number": 2, in the response always getting one less than the page number.It should return the current page index.
If you are using Spring Boot 2.X you could switch from WebMvcConfigurerAdapter to application properties. There is a set of properties to configure pageable:
# DATA WEB (SpringDataWebProperties)
spring.data.web.pageable.default-page-size=20 # Default page size.
spring.data.web.pageable.max-page-size=2000 # Maximum page size to be accepted.
spring.data.web.pageable.one-indexed-parameters=false # Whether to expose and assume 1-based page number indexes.
spring.data.web.pageable.page-parameter=page # Page index parameter name.
spring.data.web.pageable.prefix= # General prefix to be prepended to the page number and page size parameters.
spring.data.web.pageable.qualifier-delimiter=_ # Delimiter to be used between the qualifier and the actual page number and size properties.
spring.data.web.pageable.size-parameter=size # Page size parameter name.
spring.data.web.sort.sort-parameter=sort # Sort parameter name.
But please remember that even if you change the one-indexed-parameter the page response (PageImpl class) will return results with zero-based page number. It could be a little misleading.
Spring added this future as well. Just make oneIndexed parameter equals to true in your configuration file and pagination will start from page 1.By default its false and pagination starts from 0.
spring.data.web.pageable.one-indexed-parameters=true
Spring Boot will be using Spring Data under the covers.
The Spring Data class you need to configure is the following:
org.springframework.data.web.PageableHandlerMethodArgumentResolver
and in particular the following method:
http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/PageableHandlerMethodArgumentResolver.html#setOneIndexedParameters-boolean-
This will allow you to use you current UI paging as is i.e. with first page = 1.
In a Boot application I think the config may look something like:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setOneIndexedParameters(true);
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
}
To get better result, you need to extend RepositoryRestMvcConfiguration, like below
#Configuration
public class RespositoryConfiguration extends RepositoryRestMvcConfiguration {
#Override
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
resolver.setOneIndexedParameters(true);
return resolver;
}
}
Then you shall get all the links and pagination information correct except 'number', which is one less.
See the result for page=1,
"_links": {
"first": {
"href": "http://localhost:8080/user/user?page=1&size=5"
},
"self": {
"href": "http://localhost:8080/user/user"
},
"next": {
"href": "http://localhost:8080/user/user?page=2&size=5"
},
"last": {
"href": "http://localhost:8080/user/user?page=2&size=5"
}
},
"page": {
"size": 5,
"totalElements": 6,
"totalPages": 2,
"number": 0
}

Some doubts about the use of parameters into a request toward a REST web service?

I am pretty new in Spring and in REST web services and I have the following dout following a tutorial that show how to implement a RESTful web service using Spring MVC.
So, into a controller class I have this method:
#Controller
#RequestMapping("/api/categories")
public class CategoryRestController {
#RequestMapping
#ResponseBody
public CategoryList getCategories(#RequestParam("start") int start, #RequestParam("size") int size ) {
List<Category> categoryEntries = categoryService.findCategoryEntries(start, size);
return new CategoryList(categoryEntries);
}
}
This method handle HTTP GET request toward the resoruce /api/categories and return the retrieved list into JSON format (I think that it depends by the content negotiazion: if the caller put the Accept header as JSON the method return the result in JSON format, is it right?)
By the way my doubt is related the HTTP request shown in the tutorial, infact it do:
http://localhost:8080/springchocolatestore/api/categories?start=0&size=2
that is handled by the previous controller method to return a paginated list (that could be hude) in JSON format, infact I retrieve the following output:
{
"categories": [
{
"links": [
{
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/1",
"variables": [],
"templated": false,
"variableNames": []
}
],
"name": "Truffles",
"description": "Truffles",
"id": {
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/1",
"variables": [],
"templated": false,
"variableNames": []
}
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/2",
"variables": [],
"templated": false,
"variableNames": []
}
],
"name": "Belgian Chocolates",
"description": "Belgian Chocolates",
"id": {
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/2",
"variables": [],
"templated": false,
"variableNames": []
}
}
]
}
Ok, so in the request I specify the pagination parameter by categories?start=0&size=2
My doubt is related to the user of this parameter. From what I have understand (but maybe it could be wrong) the use of the parameter is against the RESTful principles. Is it true or am I mising something?
Or maybe in this specific case are valid because the parameter are not specifing an object (that have to be returned into my JSON output) but are only related to some options?
I mean that maybe I can't use parameter to specify a specific object, something like this:
// RETRIEVE THE PRODUCT WITH ID=1
http://localhost:8080/springchocolatestore/api/producs?product=1
So I think that the previous is not following the RESTfull standard because I am specifing a product object with a parameter and not accessing to it as a resource, so I have to do in this way:
http://localhost:8080/springchocolatestore/api/producs/1
Can you give me some clarification?
Tnx
REST doesn't have much to do with URLs, and using request parameters is not unRESTful.
But I agree that path variables are generally used to identify a specific resource, and parameters are generally used for search or pagination parameters.

Resources