How to i can discriminate command objects in spring-mvc Controller? For example, i have following bean-classes used as form object:
public class CreateServiceFormBean {
#NotBlank
#Length(min = 3, max = 120)
private String name;
}
public class CreateDependedServiceFormBean extends CreateServiceFormBean {
#NotNull
private Short parentServiceId;
}
Getter's and Setter's is cutted out.
#RequestMapping(method = RequestMethod.POST)
public String createService(CreateServiceFormBean form) {
if (form instanceof CreateServiceFormBean) {
System.out.println("create Service");
//new ServiceEntity(form.getName())
} else if (form instanceof CreateDependedServiceFormBean) {
System.out.println("create depended Service");
parentService = ... get parent service entity...
//new DependedServiceEntity(form.getName(), parentService)
}
return null;
}
How do this? I think about create abstract controller for this two dto's, but is not elegant..maybe can handled in one method..
And how to correct get parent service entity? Some like method ModelAttribute annotation whose return entity by id?
Thanks for Replies!
Related
I have an object, and of its attributes is a List. I want to send this object from Postman to my service. I'm using Spring 5.2.7 (Spring MVC, not SpringBoot) and Hibernate 5.4.17 and Java 8. My problem is very similar to this one: I want to send a Postman POST request with an Array: members: ["william", "eric", "ryan"]
This is the class I'm trying to pass in Postman (POST method):
public class ChatDescriptionDto {
private String chatID;
private List<String> members;
private String chatType;
public String getChatID() {
return chatID;
}
public void setChatID(String chatID) {
this.chatID = chatID;
}
public List<String> getMembers() {
return members;
}
public void setMembers(List<String> members) {
this.members = members;
}
public void addMembers(List<String> members)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.addAll(members);
}
public void addMember(String member)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.add(member);
}
public String getChatType() {
return chatType;
}
public void setChatType(String chatType) {
this.chatType = chatType;
}
}
I've tried this and it didn't work:
{
"chatID": "123",
"members": ["P2001222833","P2001640916"],
"chatType": "personal"
}
Edit: This is my controller:
#PostMapping("/initiateChat")
public String initiateChat(#RequestBody ChatDescriptionDto chat)
{
return chatServiceLocal.initiateChat(chat)?"Chat Description created":"Failure! Could not save.";
}
Edit 2: The method which I've written in the question, "members": ["P2001222833","P2001640916"], is the correct one. Turns out, there was some error in the server so it never started and I didn't check that.
Having no information about the Controller class you're using, the first thing I'd assume is that you're receiving an empty object, which means that Spring simply skipped the serialization. This is the case when you don't specify the parameter of the method as #RequestBody. First, make sure that you do have the annotation.
#RestController
#RequestMapping("/")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity test(#RequestBody ChatDescriptionDto dto) {
System.out.println(dto);
return ResponseEntity.ok().build();
}
}
If that's not the case, I'd assume that the problem is with the content type you're using. Spring uses JSON by default, but you can change it in your endpoint's configuration.
To send a simple object request, you do:
{
"member":"kola"
}
To send a list object request, you do:
{
"member": ["kola","wale","ojo"]
}
This is more like listing array elements.
Any error that pops up after this, is basically not because of the request you sent.
in the below example, i am using POST and GET methods. post is to initialize a varibale and GET is to get this varibale.
I use Postman to make the requests.
i am receiving an error
#RequestBody(value = "val") //cant resolve method value
please let me know how to fix the belwo error so i can use post method for initialization and get methdo to retrieve the value
Controller1
#Controller
#RequestMapping("/call1")
public class Call1 {
public String str = "inti";
#RequestMapping(value = "/intiparam1", method = RequestMethod.POST)
public void intiParam1(#RequestBody(value = "val") String val) {
this.str = val;
}
#RequestMapping(value = "/getparam1", method = RequestMethod.GET)
public String getParam1() {
return this.str;
}
}
Create a class Variable and use other code in controller.
class Variable {
String data= 'val';
}
#RequestMapping(value = "/intiparam1", method = RequestMethod.POST)
public void intiParam1(#RequestBody Variable val) {
this.str = val.data;
}
When making a request pass json as {"data":"12345"}
and then use #RequestBody Variable v in code instead of String as it will serve your purpose of default value and will make the code extensible as you can add different properties to the existing variable in future if needed.
When to use #RequestBody?
You can not use value with it. You can use this when you have multiple field entity which you want to perform the operation. Let's say you want to save the user then you may need to create User Model first and use in a controller #RequestBody.
Model:
public class User
{
#Id
private int id;
private String firstname;
private String lastname;
//Getters-Setters, AllArgConstructor-constructor
}
#RequestMapping(value = "/requestBodyExample", method = RequestMethod.POST)
public String intiParam1(#RequestBody User user) {
return user.getFirstname();
}
Quick Start with Spring Boot
I'm creating a Spring boot REST API which should take 2 Lists of custom objects. I'm not able to correctly pass a POST body to the API I've created. Any idea what might be going wrong ?
Below is my code :
Controller Class Method :
// Main controller Class which is called from the REST API. Just the POST method for now.
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody List<Plan> inputPlans, #RequestBody List<Feature> inputFeatures) {
logger.info("Plans received from user are : " + inputPlans.toString());
return planService.findBestPlan(inputPlans, inputFeatures);
}
Plan Class , this will contain the Feature class objects in an array:
public class Plan {
public Plan(String planName, double planCost, Feature[] features) {
this.planName = planName;
this.planCost = planCost;
this.features = features;
}
public Plan() {
}
private String planName;
private double planCost;
Feature[] features;
public String getPlanName() {
return planName;
}
// getters & setters
}
Feature POJO Class :
// Feature will contain features like - email , archive etc.
public class Feature implements Comparable<Feature> {
public Feature(String featureName) {
this.featureName = featureName;
}
public Feature() {
}
private String featureName;
// Getters / Setters
#Override
public int compareTo(Feature inputFeature) {
return this.featureName.compareTo(inputFeature.getFeatureName());
}
}
You cannot use #RequestBody twice!
You should create a class that holds the two lists and use that class with #RequestBody
You should create json like this:
{
"inputPlans":[],
"inputFeatures":[]
}
and create Class like this:
public class SolutionRequestBody {
private List<Plan> inputPlans;
private List<Feature> inputFeatures;
//setters and getters
}
POST mapping like this:
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody SolutionRequestBody solution) {
logger.info("Plans received from user are : " + solution.getInputPlans().toString());
return planService.findBestPlan(solution);
}
I am building project on spring boot and want to add validation that are easy to integrate.
I have Pojo for my project as below:
public class Employee{
#JsonProperty("employeeInfo")
private EmployeeInfo employeeInfo;
}
EmployeeInfo class is as below:
public class EmployeeInfo extends Info {
#JsonProperty("empName")
private String employeeName;
}
Info class is as below:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Info {
#JsonProperty("requestId")
protected String requestId;
}
How to I validate if request Id is not blank with javax.validation
My controller class is as below:
#RequestMapping(value = "/employee/getinfo", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<> getEmployee(#RequestBody Employee employee) {
//need to validate input request here
//for e.g to check if requestId is not blank
}
Request :
{
"employeeInfo": {
"requestId": "",
}
}
Considering you are making use of validation-api:
Please try using below to validate if your String is not null or not containing any whitespace
#NotBlank
In order to validate request parameters in controller methods, you can either use builtin validators or custom one(where you can add any type of validations with custom messages.)
Details on how to use custom validations in spring controller, Check how to validate request parameters with validator like given below:
#Component
public class YourValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(Employee.class);
}
#Override
public void validate(Object target, Errors errors) {
if (target instanceof Employee) {
Employee req = (Employee) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "employeeInfo.requestId", "YourCustomErrorCode", "yourCustomErrorMessage");
//Or above validation can also be done as
if(req.getEmployeeInfo().getRequestId == null){
errors.rejectValue("employeeInfo.requestId", "YourCustomErrorCode", "YourCustomErrorMessage");
}
}
}
}
I am working on a Spring web app and i have an entity that has an Integer property which the user can fill in when creating a new entity using a JSP form. The controller method called by this form is below :
#RequestMapping(value = {"/newNursingUnit"}, method = RequestMethod.POST)
public String saveNursingUnit(#Valid NursingUnit nursingUnit, BindingResult result, ModelMap model)
{
boolean hasCustomErrors = validate(result, nursingUnit);
if ((hasCustomErrors) || (result.hasErrors()))
{
List<Facility> facilities = facilityService.findAll();
model.addAttribute("facilities", facilities);
setPermissions(model);
return "nursingUnitDataAccess";
}
nursingUnitService.save(nursingUnit);
session.setAttribute("successMessage", "Successfully added nursing unit \"" + nursingUnit.getName() + "\"!");
return "redirect:/nursingUnits/list";
}
The validate method simply checks if the name already exists in the DB so I did not include it. My issue is that, when I purposely enter text in the field, I would like to have a nice message such as "The auto-discharge time must be a number!". Instead, Spring returns this absolutely horrible error :
Failed to convert property value of type [java.lang.String] to required type [java.lang.Integer] for property autoDCTime; nested exception is java.lang.NumberFormatException: For input string: "sdf"
I fully understand why this is happening but i cannot for the life of me figure out how to, programmatically, replace Spring's default number format exception error message with my own. I am aware of message sources which can be used for this type of thing but I really want to achieve this directly in the code.
EDIT
As suggested, i built this method in my controller but i'm still getting Spring's "failed to convert property value..." message :
#ExceptionHandler({NumberFormatException.class})
private String numberError()
{
return "The auto-discharge time must be a number!";
}
OTHER EDIT
Here is the code for my entity class :
#Entity
#Table(name="tblNursingUnit")
public class NursingUnit implements Serializable
{
private Integer id;
private String name;
private Integer autoDCTime;
private Facility facility;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
#Size(min = 1, max = 15, message = "Name must be between 1 and 15 characters long")
#Column(nullable = false, unique = true, length = 15)
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
#NotNull(message = "The auto-discharge time is required!")
#Column(nullable = false)
public Integer getAutoDCTime()
{
return autoDCTime;
}
public void setAutoDCTime(Integer autoDCTime)
{
this.autoDCTime = autoDCTime;
}
#ManyToOne (fetch=FetchType.EAGER)
#NotNull(message = "The facility is required")
#JoinColumn(name = "id_facility", nullable = false)
public Facility getFacility()
{
return facility;
}
public void setFacility(Facility facility)
{
this.facility = facility;
}
#Override
public boolean equals(Object obj)
{
if (obj instanceof NursingUnit)
{
NursingUnit nursingUnit = (NursingUnit)obj;
if (Objects.equals(id, nursingUnit.getId()))
{
return true;
}
}
return false;
}
#Override
public int hashCode()
{
int hash = 3;
hash = 29 * hash + Objects.hashCode(this.id);
hash = 29 * hash + Objects.hashCode(this.name);
hash = 29 * hash + Objects.hashCode(this.autoDCTime);
hash = 29 * hash + Objects.hashCode(this.facility);
return hash;
}
#Override
public String toString()
{
return name + " (" + facility.getCode() + ")";
}
}
YET ANOTHER EDIT
I am able to make this work using a message.properties file on the classpath containing this :
typeMismatch.java.lang.Integer={0} must be a number!
And the following bean declaration in a config file :
#Bean
public ResourceBundleMessageSource messageSource()
{
ResourceBundleMessageSource resource = new ResourceBundleMessageSource();
resource.setBasename("message");
return resource;
}
This gives me the correct error message instead of the Spring generic TypeMismatchException / NumberFormatException which i can live with but still, I want to do everything programmatically wherever possible and I'm looking for an alternative.
Thank you for your help!
You may be able to override that messaging by providing an implementation of the Spring DefaultBindingErrorProcessor similar to what is done here:
Custom Binding Error Message with Collections of Beans in Spring MVC
You can annotate a method with:
#ExceptionHandler({NumberFormatException.class})
public String handleError(){
//example
return "Uncorrectly formatted number!";
}
and implement whatever you want to do in case the exception of that type is thrown. The given code will handle exceptions happened in the current controller.
For further reference consult this link.
To make global error handling you can use #ControllerAdvice in the following way:
#ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({NumberFormatException.class})
public String handleError(){
//example
return "Uncorrectly formatted number!";
}
}
#Martin, I asked you about the version because #ControllerAdvice is available starting with version 3.2.
I would recommend you to use #ControllerAdvice, which is an annotation that allows you to write code that is sharable between controllers(annotated with #Controller and #RestController), but it can also be applied only to controllers in specific packages or concrete classes.
ControllerAdvice is intended to be used with #ExceptionHandler, #InitBinder, or #ModelAttribute.
You set the target classes like this #ControllerAdvice(assignableTypes = {YourController.class, ...}).
#ControllerAdvice(assignableTypes = {YourController.class, YourOtherController.class})
public class YourExceptionHandler{
//Example with default message
#ExceptionHandler({NumberFormatException.class})
private String numberError(){
return "The auto-discharge time must be a number!";
}
//Example with exception handling
#ExceptionHandler({WhateverException.class})
private String whateverError(WhateverException exception){
//do stuff with the exception
return "Whatever exception message!";
}
#ExceptionHandler({ OtherException.class })
protected String otherException(RuntimeException e, WebRequest request) {
//do stuff with the exception and the webRequest
return "Other exception message!";
}
}
What you need to keep in mind is that if you do not set the target and you define multiple exception handlers for the same exceptions in different #ControllerAdvice classes, Spring will apply the first handler that it finds. If multiple exception handlers are present in the same #ControllerAdvice class, an error will be thrown.
Solution 1: StaticMessageSource as Spring bean
This gives me the correct error message instead of the Spring generic TypeMismatchException / NumberFormatException which i can live with but still, I want to do everything programmatically wherever possible and I'm looking for an alternative.
Your example uses ResourceBundleMessageSource which uses resource bundles (such as property files). If you want to use everything programmatically, then you could use a StaticMessageSource instead. Which you can then set as a Spring bean named messageSource. For example:
#Configuration
public class TestConfig {
#Bean
public MessageSource messageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("typeMismatch.java.lang.Integer", Locale.getDefault(), "{0} must be a number!");
return messageSource;
}
}
This is the simplest solution to get a user friendly message.
(Make sure the name is messageSource.)
Solution 2: custom BindingErrorProcessor for initBinder
This solution is lower level and less easy than solution 1, but may give you more control:
public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor {
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
Throwable cause = ex.getCause();
if (cause instanceof NumberFormatException) {
String field = ex.getPropertyName();
Object rejectedValue = ex.getValue();
String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), field);
boolean useMyOwnErrorMessage = true; // just so that you can easily see to default behavior one line below
String message = useMyOwnErrorMessage ? field + " must be a number!" : ex.getLocalizedMessage();
FieldError error = new FieldError(bindingResult.getObjectName(), field, rejectedValue, true, codes, arguments, message);
error.wrap(ex);
bindingResult.addError(error);
} else {
super.processPropertyAccessException(ex, bindingResult);
}
}
}
#ControllerAdvice
public class MyControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
BindingErrorProcessor customBindingErrorProcessor = new CustomBindingErrorProcessor();
binder.setBindingErrorProcessor(customBindingErrorProcessor);
}
}
It basically intercepts the call to DefaultBindingErrorProcessor.processPropertyAccessException and adds a custom FieldError message when binding failed with a NumberFormatException.
Example code without Spring Web/MVC
In case you want to try it without Spring Web/MVC, but just plain Spring, then you could use this example code.
public class MyApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Validator validator = context.getBean(LocalValidatorFactoryBean.class);
// Empty person bean to be populated
Person2 person = new Person2(null, null);
// Data to be populated
MutablePropertyValues propertyValues = new MutablePropertyValues(List.of(
new PropertyValue("name", "John"),
// Bad value
new PropertyValue("age", "anInvalidInteger")
));
DataBinder dataBinder = new DataBinder(person);
dataBinder.setValidator(validator);
dataBinder.setBindingErrorProcessor(new CustomBindingErrorProcessor());
// Bind and validate
dataBinder.bind(propertyValues);
dataBinder.validate();
// Get and print results
BindingResult bindingResult = dataBinder.getBindingResult();
bindingResult.getAllErrors().forEach(error ->
System.out.println(error.getDefaultMessage())
);
// Output:
// "age must be a number!"
}
}
#Configuration
class MyConfig {
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
class Person2 {
#NotEmpty
private String name;
#NotNull #Range(min = 20, max = 50)
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public Person2(String name, Integer age) {
this.name = name;
this.age = age;
}
}
Handle NumberFormatException.
try {
boolean hasCustomErrors = validate(result, nursingUnit);
}catch (NumberFormatException nEx){
// do whatever you want
// for example : throw custom Exception with the custom message.
}