Populate query parameters from DTO - spring

Is there a way to let Spring populate RestTemplate query parameters automatically from a DTO, similarly to how it instantiates the response DTO automatically?
I wish to write something like:
RequestDto request = new RequestDto();
request.setFoo("foo");
request.setBar("bar");
ResponseDto response = restTemplate.getForObject(
"http://example.com/api",
ResponseDto.class,
request
);
Instead of:
ResponseDto response = restTemplate.getForObject(
"http://example.com/api?foo={foo}&bar={bar}",
ResponseDto.class,
"foo",
"bar"
);
Because there are many large DTOs, requiring tons of boilerplate code, which must be kept in sync with any DTO changes.
Spring 4.3.25

I don't think that is directly possible. The following is not exactly using the DTO, but it does let you build the request without having to form the URL string manually. You can use Spring's UriComponentsBuilder class.
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api")
.queryParam("foo", "bar")
// etc. ...
.queryParam("bar", "foo");
String result = restTemplate.getForObject(builder.toString(), String.class);
You could loop over the DTO and build the query as above. Or without the DTO, you could use a Map<String, String> and loop over it.
Map<String, String> params = new HashMap<>();
params.put("foo", "bar");
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api");
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
String result = restTemplate.getForObject(builder.toString(), String.class);
Edit:
As crizzis suggested below, you can use Spring Cloud OpenFeign's REST Client (from Feign #QueryMap support):
The OpenFeign #QueryMap annotation provides support for POJOs to be used as GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is incompatible with Spring because it lacks a value property.
and
Spring Cloud OpenFeign provides an equivalent #SpringQueryMap annotation, which is used to annotate a POJO or Map parameter as a query parameter map.
From your question's example:
public class RequestDto {
private string foo;
private string bar;
}
#FeignClient(name = "client", url = "http://example.com")
public interface FooTemplate {
#GetMapping(path = "/api")
String endpoint(#SpringQueryMap RequestDto requestDto);
}

You can do something like this-
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/api")
.queryParam("foo", "foo")
.queryParam("bar", "bar");
ResponseDto response = restTemplate.getForObject(
builder.buildAndExpand(builder).toUriString(),
ResponseDto.class);
A more detailed answer can be found here- RestTemplate: How to send URL and query parameters together

How about using Feign? It allows you to describe the remote endpoint just like a Spring Controller. This includes support for query parameter DTOs.
See an example here

Related

Is Springdoc swagger not possible when passing HttpServletRequest as method param?

I have below springboot rest controller and using springdoc-openapi. My springboot rest service has to cater to some legacy application( which is the only client which calls this service) and I need to have HttpServletRequest as param.
I am generating swagger openapi doc and when I got to swagger-ui.html, I see that the rerquest body comes for Httprequest param method with uri path = '/personhttprequest' but not when param is HttpServletRequest. I see here
https://springdoc.org/faq.html#what-are-the-ignored-types-in-the-documentation
But I am not clear why and how can I get HttpServletRequest param working in swagger ui. I want to pass it as text/xml just like i can make it work for HttpRequest below.I have attached scrrenshot for "/personhttprequest" and you see the box for request body as xml comes up. How can make it work for "/personhttpservletrequest"?
#RestController
public class PersonController {
#RequestMapping(path = "/personhttprequest", method = RequestMethod.POST,consumes=MediaType.TEXT_XML_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public Person personHttpRequest(HttpRequest req) {
Person person = new Person();
return person;
}
#RequestMapping(path = "/personhttpservletrequest", method = RequestMethod.POST,consumes=MediaType.TEXT_XML_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public Person personHttpServletRequest(HttpServletRequest req) {
Person person = new Person();
return person;
}
}
Here is git hub :
https://github.com/vmisra2018/sb-example-swaggerdoc
Principal, Locale, HttpServletRequest and HttpServletResponse and other injectable parameters supported by Spring MVC are excluded.
Full documentation here:
https://docs.spring.io/spring/docs/5.1.x/spring-framework-reference/web.html#mvc-ann-arguments.
If you don't want to ignore it:
SpringDocUtils.getConfig().removeRequestWrapperToIgnore(HttpServletRequest.class)

Spring's RestTemplate: complex object to query params

I have a complex object like this:
public class ComplexObject {
private String a;
private String b;
...
private String z;
//getters and setters
}
I want to call a web service that receives all the complex object fields: http://localhost:8080/api/some_service?a=something&b=something&...&z=something
Is there any way to pass a ComplexObject to RestTemplate and have the work done automatically or I have to do the manual mapping by myself?
Thanks!
YES! there is a way to pass complete complex object to make the service call and then for sure it can be achieved automatically.
And for this you have to alter the way you send this complexObject and have to use HTTP POST (highly recommended ), as:
public HttpStatus send()
{
ComplexObject complexObj = getYourFilledObject();
ResponseEntity<HttpStatus> response = restTemplate.postForEntity(ROOT_URI, complexObj, HttpStatus.class);
return response;
}
And if not and GET is the only option then unfortunately you have to send as you’re. Because at the end of the day either you use rest templates ‘s function which intake params map or you create your own URI with params, it is the same HTTP GET and you have to achieve programmatically.
For examples & illustration you can visit here and best reference will be spring resttemplate doc

How to accept RequestBody of different class types dynamically

I am using Spring Boot . Writing rest api's
where for the same api url , the request json structure varies
Is there any way we can apply Factory design or some thing else
#RequestMapping(value = "/myservice/{type}", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> myServiceApi(#PathVariable String type,
#RequestBody SomeClass1 somereq) {
// here based on type , the RequestBody can be either SomeClass1 or SomeClass2
// both the SomeClass1 and SomeClass2 has nothing in common .
}
The above code will work only if the request json is in SomeClass1 format , but i needed it to accept among {SomeClass1 , SomeClass2}
You could do this by passing the JSON as a String into your controller method and then mapping this to whichever object you expect to need:
#PostMapping(value = "/myservice/{type}")
public ResponseEntity<?> myServiceApi(#PathVariable String type,
#RequestBody String somereq) {
ObjectMapper mapper = new ObjectMapper();
if (<something that indicates SomeClass1>) {
SomeClass1 someClass1 = mapper.readValue(somereq, SomeClass1.class);
} else if (<something that indicates SomeClass2>) {
SomeClass2 someClass2 = mapper.readValue(somereq, SomeClass2.class);
}
}
Although to be honest if you really are expecting bodies with completely different structures my advice would be to just make separate API calls for these.

how to capture multiple parameters using #RequestParam using spring mvc?

Suppose a hyperlink is clicked and an url is fired with the following parameter list myparam=myValue1&myparam=myValue2&myparam=myValue3 . Now how can I capture all the parameters using #RequestParam in spring mvc?
My requirement is I have to capture all the params and put them in a map.
Please help!
#RequestMapping(value = "users/newuser", method = RequestMethod.POST)
public String saveUser(#RequestParam Map<String,String> requestParams) throws Exception{
String userName=requestParams.get("email");
String password=requestParams.get("password");
//perform DB operations
return "profile";
}
You could use RequestParam in the above mentioned manner.
It seems you can't get
Map<String,String>
because all your params have same name "myparam"
Try this instead:
public ModelAndView method(#RequestParam("myparam") List<String> params) { }
To get all parameters at once try this:
public ModelAndView postResultPage(#RequestParam MultiValueMap<String, String> params)
This feature is described in the #RequestParam java doc (3. Paragraph):
Annotation which indicates that a method parameter should be bound to a web request parameter. Supported for annotated handler methods in Servlet and Portlet environments.
If the method parameter type is Map and a request parameter name is specified, then the request parameter value is converted to a Map assuming an appropriate conversion strategy is available.
If the method parameter is Map<String, String> or MultiValueMap<String, String> and a parameter name is not specified, then the map parameter is populated with all request parameter names and values.
As of Spring 3.0, you can also use MultiValueMap to achieve this:
A rudimentary example would be:
public String someMethod(#RequestParam MultiValueMap<String,String> params) {
final Iterator<Entry<String, List<String>>> it = params.entrySet().iterator();
while(it.hasNext()) {
final String k = it.next().getKey();
final List<String> values = it.next().getValue();
}
return "dummy_response";
}
If anyone is trying to do the same in Spring Boot, use RequestBody in place of RequestParam
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
You can use for multiple Params as such
public String saveUser(#RequestParam("email") String userName, #RequestParam("password") String password) throws Exception{
//your code
//perform DB operations
return "profile";
}
For params with same name, you can use MultiValueMap<String ,String>. Then all the values would be present as List
You can use multiple #RequestParam annotations as shown below.
#RequestParam(value="myparam1", required = true) <Datatype> myparam1,
#RequestParam(value = "myparam2", required = false) <Datatype> myparam2,

How to POST JSON object to spring controller?

I have spring controller:
#RequestMapping(value = "/add", method = RequestMethod.POST,
consumes = "application/json")
public #ResponseBody ResponseDto<Job> add(User user) {
...
}
I can POST the object like this with APACHE HTTP CLIENT:
HttpPost post = new HttpPost(url);
List nameValuePairs = new ArrayList();
nameValuePairs.add(new BasicNameValuePair("name", "xxx"));
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = client.execute(post);
In controller I get user with name "xxx"
Now I want to create User object and post it to the server,
I tried to use with GSON object like this :
User user = new User();
user.setName("yyy");
Gson gson = new Gson();
String json = gson.toJson(user);
HttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(json.toString(), HTTP.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);
HttpResponse response = client.execute(httpPost);
But in this way I get in server User object with null fields...
How can I solve it ?
Ok a few things you're missing:
Make sure you are serializing and deserializing User to json the same way on the client and server.
Make sure to have jackson libraries on the classpath if you want to use spring built-in jackson support (and preferably use that on the client as well) or include apropriate HttpMessageConverter for Gson. You can use GsonHttpMessageConverter from spring-android for that.
Annotate your request handler method parameter with #RequestBody.
In case of using jackson, as #ararog mentioned, make sure that you specifically exclude fields that can be ingored or annotate the whole User class with #JsonIgnoreProperties(ignoreUnknown = true)
As far as I know, Spring MVC uses Jackson for JSON parsing and serialization/deserialization, jackson usually expects the for a JSON content which has data for all class properties, except those who are marked with JSON ignore, like below:
public class User {
private String login;
private String name;
#JsonIgnoreProperty
private String password;
... getters/setters...
}
So, if you create a instance of User an set only the user name and send this data to server, Jackson will try to deserialize the content to another User object on server side, during the deserialization process he will consider the two mandatory properties login and name, since only name is filled the deserialization is finished and a null reference is returned to the controller.
You have two options:
As an test, set a fake value in all the other properties and send the user data again
Create a Jackson mix-in and add anotations to ignored properties.

Resources