Conditional JSON Request mapping in Spring boot - java

I have a post request with URL http://my-custom-url/{param}
now
//pseudo code
if param == param1
then request body must be mapped to model Class A
if param == param2
then request body must be mapped to model Class B
In addition to mapping I also have Jsr303 validation inside my model class es hence I also want to use #Valid annotation
How do I do this

You can receive the payload as a Map<K, V> and use ObjectMapper to deserialize it.
#RestController
#RequestMapping(path = "/my-rest-api")
public class MyController {
#Autowired
ClassAValidator classAValidator; // assuming you wrote an validator
#Autowired
ClassBValidator classBValidator; // assuming you wrote an validator
#PostMapping("/{parameter}")
public ResponseEntity<Object> handleRequest(
#PathVariable("parameter") String parameter,
#RequestBody Map<Object, Object> request,
BindingResult result) {
ObjectMapper mapper = new ObjectMapper();
if (parameter.equals("X")) {
ClassA classA = mapper.convertValue(request, ClassA.class);
classAValidator.validate(classA, result);
if(result.hasErrors()) {
throw new CustomException(result);
}
} else if (parameter.equals("Y")) {
ClassB classB = mapper.convertValue(request, ClassB.class);
classBValidator.validate(classB, result);
if(result.hasErrors()) {
throw new CustomException(result);
}
} else {
// ..
}
return ResponseEntity.ok("Ok");
}
}

I think this one is a simpler solution:
#RestController
#RequestMapping("/my-custom-url")
public class MyController {
#PostMapping("/param1")
public ResponseEntity<Object> handleClassA(
#Valid #RequestBody ClassA classA,
BindingResult result) {
...
return ResponseEntity.ok("Ok");
}
#PostMapping("/param2")
public ResponseEntity<Object> handleClassB(
#Valid #RequestBody ClassB classB,
BindingResult result) {
...
return ResponseEntity.ok("Ok");
}
}

I am not sure why above answers explaining validators. as I understand you are trying to return two type of responses based on condition. that is not possible. because return type validate on compile time. so if you set return type as classA you must return classA. you cannot return classB. (unless classB is a classA relationship) so if you really want to do this you can use name interface to enforce the type.
#GetMapping(value = "/{id}")
public Response getRent(#PathVariable int id, #RequestParam(required = false) String type) {
if(type=="A"){
return new ClassA(rentService.findById(id));
}else{
return new ClassB (rentService.findDetailResponse(id));
}
}
P.S: you may need to return this as
public ResponseEntiry<Response>
but I just used Response directly.
now you need to create those two classes from same interface
public interface Response {
}
public class ClassA implements Response {
// your code goes here
}
public class ClassB implements Response {
// code here
}
this way you can achieve what you are trying.

Related

How Does #ResponseBody annotation binds data on void methods in a controller?

I searched for the use of #ResponseBody annotation and I found that this annotation binds data to the response according to the return type of the method here(https://zetcode.com/springboot/responsebody/),But today I got an old project where I saw a controller method like this:-
#Controller
public class MyConroller {
#Autowired
JsonMapper mapper;
#RequestMapping(value = "/methodURL", method = RequestMethod.GET)
#ResponseBody
public void controllerMethod(HttpServletRequest req, HttpServletResponse res) {
HttpSession hs = req.getSession(false);
Map<String, Object> map = service.getList();
mapper.WritecInJson(res, map);
}
}
JASON Mapper-:
public class JsonMapper {
public void WritecInJson(HttpServletResponse res,Object object)
{
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
if(jsonConverter.canWrite(object.getClass(), jsonMimeType)){
jsonConverter.write(object , jsonMimeType, new ServletServerHttpResponse(res));
}
}
As you can see the method in the controller class has void as its return type. So, How #ResponseBody binds data to the response through this void method?
#ResponseBody
must defined a return type. If you really don’t need to define a method with return type. You can use HttpServletResponse.getOutputStream() to write back the response content.

Map as parameter in RestAPI Post request

I have created an API with a Map<String, Integer> parameter, like this:
#RequestMapping(value = "upload", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(#RequestParam("file") MultipartFile file,
#RequestParam("name") String name,
#RequestParam("campaignAppItemId") Long campaignAppItemId,
#RequestParam("fileColumnHeaders") Map<String,Integer> fileColumnHeaders) throws Exception {
if (file == null)
return new ResponseEntity<>("No file uploaded", HttpStatus.BAD_REQUEST);
contactService.handleContactsFile(file, name, campaignAppItemId,fileColumnHeaders);
return new ResponseEntity<>("File uploaded successfully", HttpStatus.OK);
}
I am trying to call this via Postman:
I passed the fileColumnHeaders inside Body->Form Data as in the screenshot.
Then I got a message like this in Postman:
Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found.
Anybody know why this message came ?
How can we pass a map as a parameter in Rest API request?
How can we pass a map through Postman?
You could use #RequestBody instead of #RequestParam for Maps and other non trivial data types and objects - this way spring will map the JSON representing your map parameter to a domain object, which is then serializable and can be converted to a java object.
... Or simply create a converter:
#Component
#RequiredArgsConstructor
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, String>>() {
});
} catch (final IOException e) {
return null;
}
}
}
Firstly, you create DTO object to get all data from your request.
public class FormDataDTO {
private MultipartFile file;
private String name;
private Long campaignAppItemId;
private Map<String,Integer> fileColumnHeaders;
// getters, setters
}
Secondly, you can map FormDataDTO from your request without any annotation:
#RequestMapping(value = "upload", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(FormDataDTO formDataDTO){
// your logic code here
}
Finally, form-data in your request will be:
I think this could work:
#RequestMapping(value = "upload/{fileColumnHeaders}", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(#RequestParam("file") MultipartFile file,
#RequestParam("name") String name,
#RequestParam("campaignAppItemId") Long campaignAppItemId,
#MatrixVariable Map<String,Integer> fileColumnHeaders) throws Exception {
if (file == null)
return new ResponseEntity<>("No file uploaded", HttpStatus.BAD_REQUEST);
contactService.handleContactsFile(file, name, campaignAppItemId,fileColumnHeaders);
return new ResponseEntity<>("File uploaded successfully", HttpStatus.OK);
}
Put all other parameters into the body, but add the fileColumnHeaders to the URL like this:
/upload/firstName=1;lastName=2;address=3;phone=4
You will also need this extra configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}

Spring Boot using Json as request parameters instead of an entity/model

Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}

How to decorate all requests to take a value from header and add it in the body parameter?

Background
I'm creating RESTful services using Spring MVC. Currently, I have the following structure for a controller:
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
public ResponseEntity<MyEntity> updateMyEntity(
#PathVariable Long id,
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
public ResponseEntity<MyEntity> partialUpdateMyEntity(
#PathVariable Long id,
#RequestBody MyEntity myEntity,
#RequestHeader("X-Client-Name") String clientName) {
myEntity.setClientName(clientName);
//rest of method declaration...
}
}
As you can see, all these three methods receive the same parameter for the header #RequestHeader("X-Client-Name") String clientName and applies it in the same way on each method: myEntity.setClientName(clientName). I will create similar controllers and for POST, PUT and PATCH operations will contain almost the same code but for other entities. Currently, most entities are designed to support this field vía a super class:
public class Entity {
protected String clientName;
//getters and setters ...
}
public class MyEntity extends Entity {
//...
}
Also, I use an interceptor to verify that the header is set for requests.
Question
How can I avoid repeating the same code through controller classes and methods? Is there a clean way to achieve it? Or should I declare the variable and repeat those lines everywhere?
This question was also asked in the Spanish community. Here's the link.
My suggestion is to store the header value in the request scoped bean inside the Spring interceptor or filter. Then you may autowire this bean wherever you want - service or controller and use the stored client name value.
Code example:
public class ClientRequestInterceptor extends HandlerInterceptorAdapter {
private Entity clientEntity;
public ClientRequestInterceptor(Entity clientEntity) {
this.clientEntity = clientEntity;
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String clientName = request.getHeader("X-Client-Name");
clientEntity.setClientName(clientName);
return true;
}
}
In your configuration file:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(clientRequestInterceptor());
}
#Bean(name="clientEntity")
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Entity clientEntity() {
return new Entity();
}
#Bean
public ClientRequestInterceptor clientRequestInterceptor() {
return new ClientRequestInterceptor(clientEntity());
}
}
Then, lets assume we have to use this bean in our controller:
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController {
#Autowired
private Entity clientEntity; // here you have the filled bean
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(#RequestBody MyEntity myEntity) {
myEntity.setClientName(clientEntity.getClientName());
//rest of method declaration...
}
// rest of your class methods, without #RequestHeader parameters
}
I have not compiled this code, so correct me if I made some mistakes.
I've got an interesting answer in the Spanish site (where I also posted this question) and based on that answer I could generate mine that adapts to this need. Here's my answer on SOes.
Based on #PaulVargas's answer and an idea from #jasilva (use inheritance in controller) I though on a stronger solution for this case. The design consists of two parts:
Define a super class for controllers with this behavior. I call this class BaseController<E extends Entity> because Entity is the super class for almost al my entities (explained in the question). In this class I'll retrieve the value of #RequestBody E entity parameter and assign it into a #ModelAttribute parameter like #PaulVargas explains. Generics power helps a lot here.
My controllers will extend BaseController<ProperEntity> where ProperEntity is the proper entity class I need to handle with that controller. Then, in the methods, instead of injecting #RequestBody and #RequestHeader parameters, I'll only inject the the #ModelAttribute (if needed).
Aquí muestro el código para el diseño descrito:
//1.
public abstract class BaseController<E extends Entity> {
#ModelAttribute("entity")
public E populate(
#RequestBody(required=false) E myEntity,
#RequestHeader("X-Client-Name") String clientName) {
if (myEntity != null) {
myEntity.setCreatedBy(clientName);
}
return myEntity;
}
}
//2.
#RestController
#RequestMapping(path = "myEntity", produces="application/json; charset=UTF-8")
public class MyEntityController extends BaseController<MyEntity> {
#RequestMapping(path={ "", "/"} , method=RequestMethod.POST)
public ResponseEntity<MyEntity> createMyEntity(
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PUT)
public ResponseEntity<MyEntity> updateMyEntity(
#PathVariable Long id,
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
#RequestMapping(path={ "/{id}"} , method=RequestMethod.PATCH)
public ResponseEntity<MyEntity> partialUpdateMyEntity(
#PathVariable Long id,
#ModelAttribute("entity") MyEntity myEntity) {
//rest of method declaration...
}
}
In this way, I don't need to rewrite those lines of code in every method and controller, achieving what I've asked.
You could consider using RequestBodyAdvice. See the javadocs.
The HttpInputMessage object where you can access the http headers, is passed into the interface methods.

Removing duplication from Spring controllers

I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.

Categories

Resources