I was reading the Spring docs and found that creating a subclass from ResponseEntityExceptionHandler was a good way on handling exceptions. However, I tried to handle exceptions in a different way, since I need to diff BusinessExceptions from TechnicalExceptions.
Created a bean called BusinessFault which encapsulates the exception details:
BusinessFault.java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonInclude(value = Include.NON_NULL)
public class BusinessFault {
#JsonProperty(value = "category")
private final String CATEGORY = "Business Failure";
protected String type;
protected String code;
protected String reason;
protected String description;
protected String instruction;
public BusinessFault(String type, String code, String reason) {
this.type = type;
this.code = code;
this.reason = reason;
}
public BusinessFault(String type, String code, String reason, String description, String instruction) {
this.type = type;
this.code = code;
this.reason = reason;
this.description = description;
this.instruction = instruction;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInstruction() {
return instruction;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
public String getCATEGORY() {
return CATEGORY;
}
}
Created a BusinessException class, which do the job by creating a BusinessFault beans through the details passed by its constructor:
BusinessException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
public abstract class BusinessException extends RuntimeException {
private BusinessFault businessFault;
public BusinessException(String type, String code, String reason) {
this.businessFault = new BusinessFault(type, code, reason);
}
public BusinessException(String type, String code, String reason, String description, String instruction) {
this.businessFault = new BusinessFault(type, code, reason, description, instruction);
}
public BusinessException(BusinessFault businessFault) {
this.businessFault = businessFault;
}
public BusinessFault getBusinessFault() {
return businessFault;
}
public void setBusinessFault(BusinessFault businessFault) {
this.businessFault = businessFault;
}
}
Created a specific UserNotFoundException class, which extends from BusinessException class:
UserNotFoundException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(BusinessFault businessFault) {
super(businessFault);
}
public UserNotFoundException(String reason) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
}
public UserNotFoundException(String reason, String description, String instruction) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
instruction);
}
}
Created a BusinessExceptionHandler, but instead of being a subclass of ResponseEntityExceptionHandler, it's only has a #ControllerAdvice annotation and a method that handles all thrown BusinessExceptions:
BusinessExceptionHandler.java
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(BusinessException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
}
}
The service layer can throw a UserNotFoundException:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public User findById(Long id) {
User user = userRepository.findOne(id);
if (user == null)
throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");
return user;
}
}
It works fine. But I was wondering if this is a bad practice on handling exceptions?
I've got a little problem with your exception handling. Principally it is absolutely ok to catch runtime exceptions, handle them and send them forth to the client, which is probably someone using your REST service and getting the error response as a JSON object. If you manage to tell him what he did wrong and what he can do about it, great! Of course, it will add some complexity to it, but it is probably easy and comfortable to work with that API.
But think about the backend developers, too, that work with your code. Especially the public User findById(Long id) method in your UserService is obscure. The reason for this is that you made your BusinessException, in particular, the UserNotFoundException unchecked.
If I joined your (backend) team, and I was to write some business logic using that service, I'd be quite sure what I had to expect from that method: I pass a user ID and get back a User object if it was found or null if not. That's why I would write code like that
User user = userService.findById("42A");
if (user == null) {
// create a User or return an error or null or whatever
} else {
// proceed
}
However, I would never know, that the first condition will never be true since you never return null. How should I know that I had to catch an Exception?
Is the compiler telling me to catch it? No, as it is not checked.
Would I look into your source code? Hell, no! Your case is extremely simple. That UserNotFoundException may be raised in another method in another class among hundred lines of code. Sometimes I couldn't look inside it, anyway, as that UserService is just a compiled class in a dependency.
Do I read the JavaDoc? Hahaha. Let's say, 50% of the time I wouldn't, and the other 50% you've forgotten to document it, anyway.
So, the developer has to wait until his code is used (either by a client or in Unit tests) to see that it doesn't work as he intended, forcing him to redesign what he has coded so far. And if your whole API is designed that way, that unchecked exceptions pop out of nowhere, it can be very very annoying, it costs time and money and is so easy to avoid, actually.
I use a similar way to handle exceptions. But in my case, different handlers are managed according to the error status (e.g. an user exists, an user cannot be registered due to some unsatisfied condition, etc.).
You also might add your generic BusinessException for some special cases. Hope it helps you feel better.
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(PreconditionFailedException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
}
#ExceptionHandler(ResourceAlreadyExistsException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
}
}
Related
I want to create a website/API, that reads in a csv and returns the wanted resoureces. I use SpringBoot: the Spring Web dependency. For reading in the csv I import implementation('com.opencsv:opencsv:5.6') to dependencies in my build.gradle-file. I decided to use the following structure:
Four java-files in src\main\java\com\example\so:
The bean Car.java:
package com.example.so;
import com.opencsv.bean.CsvBindByName;
import java.math.BigDecimal;
public class Car {
#CsvBindByName
private int id;
#CsvBindByName(column = "name")
private String brand;
#CsvBindByName
private BigDecimal price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
#Override
public String toString() {
return "Car{" +
"id=" + id +
", brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
To display the correct car I use CarController.java:
package com.example.so;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CarController {
#GetMapping("/car/{id}")
public Car findcar(#PathVariable int id) {
for (Car car: ReadInCSV.cars){
if (car.getId()==id){
System.out.println(car.toString());
return car;
}
}
return null;
}
#GetMapping("")
public String findcar() {
return "Hi!";
}
}
To read in the csv-file ReadInCSV.java:
package com.example.so;
import com.opencsv.bean.CsvToBeanBuilder;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.List;
public class ReadInCSV {
static final String fileName="src/main/resources/static/abc.csv";
static List<Car> cars;
static {
try {
cars= new CsvToBeanBuilder(new FileReader(fileName)).withType(Car.class).build().parse();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
and to start the webservice SoApplication.java:
package com.example.so;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class SoApplication {
public static void main(String[] args) {
SpringApplication.run(SoApplication.class, args);
}
}
The file with the data abc.csv:
id,name,price
1,Audi,52642
2,Mercedes,57127
3,Skoda,9000
4,Volvo,29000
5,Bentley,350000
6,Citroën,21000
7,Füll,41400
8,Rosé,21600
9,Toyota,26700
It works more or less fine, but when you enter http://localhost:8080/car/6, my browser (Firefox) displays "Citroën" instead of "Citroën". Reading in the csv seems to work fine, because when you print the bean using its .toString() you get Car{id=6, brand='Citroën', price=21000}. So apparently the json-conversion is the problem. What can I do to solve this issue?
I am new to world of java-web, so feel free telling me if there are some other problems with my approach.
I don't think this is a problem with JSON conversion but with your character encoding. Make sure both your CSV file and the output to JSON are in the same encoding, it's probably best to use UTF8. If you explicitly set produces = APPLICATION_JSON_VALUE on your controller annotation, it should already use UTF8 for that. So if it then still fails, the question remains whether the CSV is in UTF8.
Also, if you don't know what I'm talking about, https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ is pretty much a must-read.
In my Spring Boot project, I have a POJO class for reading yaml config file.
#Configuration
#ConfigurationProperties("filenet")
#Data
public class ApplicationConfig {
#NotNull(message = "Input directory cannot be null")
#NotBlank(message = "Input directory cannot be blank")
private String **inputDir**;
#NotNull(message = "Working directory cannot be null")
#NotBlank(message = "Working directory cannot be blank")
private String **workingDir**;
#Pattern(regexp = "[0-9]+",message = "Invalid value for SMTP port")
private String port;
}
Sometimes it happens that either inputDir or workingDir or other fields of the config file are blank. I'm using javax.validation.constraints to check for blank or null. When so, and when application is started, I see an exception message and program is terminated.
ex: Port has validation that it has to be a number.
What I would like to do is to gracefully handle this exception and send an email to concerned team to take care of it.
I have created a class where I'm validating the contents of config file
#Component
public class ConfigParamValidation {
public List<String> validateApplicationConfigFile(ApplicationConfig applicationConfig) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ApplicationConfig.ContentEngine>> contentEngineVoilations = validator.validate(applicationConfig.contentEngine);
exceptionMessgae = contentEngineVoilations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
if(!exceptionMessgae.isEmpty()) {
through new ConfigFileException(<by passing all required params>);
}
}
}
I have tried to create a class which extends RuntimeException
public class ConfigFileException extends RuntimeException {
private String message;
private List<String> details;
private String hint;
private String nextActions;
private String support;
private HttpStatus httpStatus;
private ZonedDateTime zonedDateTime;
public ConfigFileException(String message) {
super(message);
}
public ConfigFileException(String message, Throwable cause) {
super(message, cause);
}
public ConfigFileException(String message, Throwable cause, List<String> details, String hint, String nextActions, String support, HttpStatus httpStatus, ZonedDateTime zonedDateTime) {
super(message, cause);
this.message = message;
this.details=details;
this.hint=hint;
this.nextActions=nextActions;
this.support=support;
this.httpStatus=httpStatus;
this.zonedDateTime = zonedDateTime;
}
}
Another class with #ExceptionHandler
#Data
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupportTeamException {
#ExceptionHandler(value = {ConfigFileException.class})
public ResponseEntity<Object> handleConfigFileException(ConfigFileException e) {
ConfigFileException configFileException = new ConfigFileException(e.getMessage(), e, e.getDetails(), e.getHint(), e.getNextActions(), e.getSupport(), e.getHttpStatus(), e.getZonedDateTime());
return new ResponseEntity<Object>(configFileException,e.getHttpStatus());
}
}
The problem is that for some reason control is not passed to my SupportTeamException class where I could send email.
Or is there a better way to handle this?
It was my understanding that #ControllerAdvice only works for components that are annotated with #Controller or #RestController.
Since the validation happens at start up of your spring boot app (and not as a result of a http request), it will never go into it. What you could do is create a Component with a #PostConstructor method. (See below) I would strongly recommend to inject your Validator rather than building it yourself (to utilise Spring Boot's full potential).
What I don't fully understand is why you would want to handle this exception gracefully. If your application starts without required properties, it will just fail further down the line when the application actually uses the property. If depending on circumstances (like the environment), certain properties don't need to be there, I would recommend using Validation groups
Finally, small aside #NotBlank will also check it's not null. You don't need both annotations, unless you want to be really specific with your messages.
package com.yourpackage;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Set;
import static java.util.stream.Collectors.joining;
#Component
public class PropertiesValidator {
private final Validator validator;
public final YourProperties properties;
public PropertiesValidator(final Validator validator, final YourProperties properties) {
this.validator = validator;
this.properties = properties;
}
#PostConstruct
public void propertiesShouldBeValid() {
final Set<ConstraintViolation<YourProperties>> constraintViolations = validator.validate(properties);
if (!constraintViolations.isEmpty()) {
final String message = constraintViolations.stream()
.map(ConstraintViolation::getMessage)
.collect(joining(","));
throw new ValidationException(message); //Or send your email
}
}
}
im trying to retrieve a get response from IMDB.com's or AnimeNewsNetwork's APIs using java EE
i started hacking this guy's really nice example of connecting to a weather website API (had to install vaadin view framework)
it looks like i've made some progress (especially in understanding what's going on) but im at a problem for a long time now (3 hours although a lot of that was failing to get github to work....)
i am getting, when i try to run the application:
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
i pasted
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.15</version>
</dependency>
into the pom thing and i found a button in netbeans that "checks for problems" then it automatically updated my project with dependencies which took ages and was rather shocking and i thought it'd work then
then i tried changing .request(MediaType.TEXT_HTML) back and forth to APPLICATION_JSON (imdb defaults to json) and TEXT_HTML_TYPE or whatever i tried.
lastly i tried changing my query input from a correct movie ID tt1285016 to an incorrect one to see if that would give a different result when it only returns 2 parameters instead of 20. this brings me to the question: how does my application know how to map the response from the API to the class? a normal response is a JSON string that has 20 fields so how does my application know where to put them? maybe this is the missing link. i just changed the movie classes attributes to Response and Error to match the 2 returns from sending an incorrect movie ID to the API but still same problem
(hours later): tried to run using the anime news network's API instead and getting similar error which differs in saying "messagbodyreader not found" for text/xml instead of text/html...
i've also tried adding a few more things to the pom file from jersey dependencies document and well one of them makes my project stop deploying even after i delete and rebuild it so i've had fun with that.
below i will paste the code i am using. please note i am just hacking this guy's example up trying to get my basic functionality working and avoiding changing anything else so i can understand what i'm supposed to do, before rewriting it from scratch just to prototype before beginning to plan the actual project!! i have a lot more tutorials/examples to look at in the future so don't worry about my code other than the problem i'm stuck on :/
this is the "view" java class that uses vaadin
VaadinUI.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
JsonService.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
ForecastDisplay.java
package org.example;
import java.util.Calendar;
import java.util.Date;
import org.example.domain.Forecast;
import org.example.domain.ForecastResponse;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.label.RichText;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
*
* #author Matti Tahvonen <matti#vaadin.com>
*/
public class ForecastDisplay extends MVerticalLayout {
String mainTemplate = "Tomorrow in there will be ";
public ForecastDisplay() {
}
public void setForecast(ForecastResponse fr) {
// removeAllComponents();
removeAllComponents();
addComponents(
new Header(String.format(mainTemplate,fr.getMovie().getMovieInfo(),
fr.getMovie().getMovieInfo()))
);
//
// for (Forecast f : fr.getList()) {
// cal.add(Calendar.DAY_OF_MONTH, 1);
// Date date = cal.getTime();
// Double temperature = f.getTemp().getDay();
// String desc = f.getWeather().get(0).getDescription();
// String md = String.format(detailTemplate, date, temperature, desc);
// addComponent(new RichText().withMarkDown(md));
}
}
ForecastResponse.java class
package org.example.domain;
import java.util.ArrayList;
public class ForecastResponse {
// private String cod;
// private Double message;
// private City city;
// private Integer cnt;
// private java.util.List<Forecast> list = new ArrayList<>();
private Movie movie;
//
// public String getCod() {
// return cod;
// }
//
// public void setCod(String cod) {
// this.cod = cod;
// }
//
// public Double getMessage() {
// return message;
// }
//
// public void setMessage(Double message) {
// this.message = message;
// }
//
// public City getCity() {
// return city;
// }
//
// public void setCity(City city) {
// this.city = city;
// }
public Movie getMovie()
{
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
//
// public Integer getCnt() {
// return cnt;
// }
//
// public void setCnt(Integer cnt) {
// this.cnt = cnt;
// }
//
// public java.util.List<Forecast> getList() {
// return list;
// }
//
// public void setList(java.util.List<Forecast> list) {
// this.list = list;
// }
}
Movie.java class
package org.example.domain;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Generated;
public class Movie {
private String Response;
private String Error;
public String getMovieInfo() {
return Response;
}
public void setMovieInfo(String name) {
this.Response = Response;
}
public String getMovieInfo2() {
return Error;
}
public void setMovieInfo2(String name) {
this.Error = Error;
}
}
the guy's project before i cut it up (you need vaadin plugin to run it)
https://github.com/mstahv/consuming-rest-apis
just to reiterate i am getting
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
when i try to run the application and have tried a number of solutions... but i think its because of something i changed because i had no problems getting the guy's original files to run
i have uploaded my current version to mirrorcreator (can't get git to work...sigh) : https://www.mirrorcreator.com/files/KQF7SBBH/restapiexampletest1zip._links
thankyou for reading . i have searched google a lot but not able to fix it using the suggestions like change the poms file or whatever....
My Spring Controller of Spring JSON application returns a JSONObject. On accessing the url, i am getting 406 error page.
It works when i return String or ArrayList.
Spring Controller:
package com.mkyong.common.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
#Controller
public class JSONController {
#RequestMapping("/test")
#ResponseBody
public JSONObject test() {
try {
JSONObject result = new JSONObject();
result.put("name", "Dade")
.put("age", 23)
.put("married", false);
return result;
} catch (JSONException ex) {
Logger.getLogger(JSONController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
How can i resolve this issue? Thanks for help. I am new to Spring MVC, couldn't found resolution to this issue in the existing SO answers.
You're trying to manually do something that Spring MVC already it automatically for you. Spring automatically deduces a representation of the returning type and does a converstion. How it does it you can learn from http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc. In your case its converting to JSON.
It works when i return String or ArrayList
What happens under the hood is that Spring MVC is using Jackson library, to convert the return type to JSON. And since it has no issue converting the String or List type, all works OK.
What happens in the code you've posted is that, Jackson's object mapper is trying to convert JSONObject instance to JSON, and this fails, cause jackson expects a POJO object which JSONObject instance isn't.
To have it work you should simply write your POJO and return it. So something like
public class Person {
private String name;
private Integer age;
private Boolean married;
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 Boolean getMarried() {
return married;
}
public void setMarried(Boolean married) {
this.married = married;
}
}
and have your method changed to
#RequestMapping("/test")
#ResponseBody
public Person test() {
Person person = new Person();
person.setName("Dade");
person.setAge(23);
person.setMarried(false);
return person;
}
For what concerns your error, the same exception you will see in the working example if you for example delete getters and setters, or name them wrongly, an exception happens while trying to convert to a representation and you get a 406 error
I think you need to set headers in #RequestMapping and return HashMap.
#RequestMapping(value = "json", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody
Map<String, String> helloJson() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
return map;
}
I have extension of org.springframework.validation.Validator.
public class MyValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
...
}
}
My goal is to pass more than one target to method.
I don't like idea with overload validate method because it smells as bad code:
validate(Object target1, Object target1, Errors errors) or creating map with needed targets.
It will be good to know better approach regarding this case.
I did not try the following code, but it demonstrates a basic idea how one field of the bean could be verified against the other. Hopefully, it will help you
Let's say you have the following form bean
public class MyForm {
private String id;
private List<String> oldIds;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getOldIds() {
return oldIds;
}
public void setOldIds(List<String> oldIds) {
this.oldIds = oldIds;
}
}
and the id property has to be validated against the oldIds object (if i did understand your requirements correctly). To achieve it your need to create a constraint and mark your bean. So, the first is the constraint interface
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyConstraintValidator.class)
#Documented
public #interface MyConstraint {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
next, you need to implement the constraint validator class:
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.PropertyUtils;
public class MyConstraintValidator implements
ConstraintValidator<MyConstraint, Object> {
private String firstAttribute;
private String secondAttribute;
#Override
public void initialize(final MyConstraint constraintAnnotation) {
firstAttribute = constraintAnnotation.value()[0];
secondAttribute = constraintAnnotation.value()[1];
}
#Override
public boolean isValid(final Object object,
final ConstraintValidatorContext constraintContext) {
try {
final String id = (String) PropertyUtils.getProperty(object,
firstAttribute);
List<String> oldIds = (List<String>) PropertyUtils.getProperty(
object, secondAttribute);
// do your validation
return true;
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
}
finally, apply the created constraint to the form bean
#MyConstraint(value = { "id", "oldIds" })
public class MyForm {
// the code
}
For now, your mark your bean with the #Valid annotation from the javax.validation package or feed it to the validator object
We use a target bean which holds all the data which need to be validated. Something like
private static final class ParamsBean {
String id;
List<String> oldIds;
}
Then we simply cast the object. It's the cleanest possible solution imo, as it does not use generic Map or List of unknown objects (though the casting still is not nice).
i faced with a similar situation where i need to pass more arguments to the validate method so i came up with a idea of my own.in my case i wanted a String to be passed to this method
validate method implemented in the following classes CustomValidatorBean, LocalValidatorFactoryBean, OptionalValidatorFactoryBean, SpringValidatorAdapter
I extended the CustomValidatorBean and called the validate method in super class and it is working perfectly
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile)
{
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}