Changed & Improved passing data from WebServlet to a WebService - java

I'm pretty new to writing Servlet and REST Services, but now i'm at a problem that I'm not sure if i'm doing it correctly. My Service look like this:
#POST
#Produces ("application/json")
#Path ("/register")
public String register(
#FormParam("name") String name,
#FormParam("username") String username,
#FormParam("password") String password,
#Context HttpServletResponse servletResponse) throws IOException {
if( this.user_taken(username) ) return "USERNAME_TAKEN";
User user = new User(name,username,password);
.....
return mapper.writeValueAsString(user);
}
So as you can see the Service takes care of doing the back end (database and creating user) the Servlet on the other hand is in charge of taking request from the form, properly validating and passing it to the Service. Servlet Code:
... validate user input form ...
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource("http://localhost/Jaba");
String map = mapper.writeValueAsString(request.getParameterMap());
MultivaluedMap<String, String> obj = mapper.readValue(map, MultivaluedMap.class);
String result =
service.path("api").path("register")
.accept("application/json")
.post(String.class, obj);
As you can see the Client (Servlet) has to do a lot of nasty work, to pass data to the Service. How can this be changed/improved/optimized or better yet refactored ? I'm trying to follow best practices and how it would be in a real life example.

Here is what I might do:
Instead of doing
String result =
service.path("api").path("register")
.accept("application/json")
.post(String.class, obj);
I would do something more like creating a DTO object, filling it out and then passing it to your service. This is then were you would apply an aspect along with JSR validation and annotations (you can do this on what you have but it won't be nearly so nice) on the client call.
example:
#Aspect
public class DtoValidator {
private Validator validator;
public DtoValidator() {
}
public DtoValidator(Validator validator) {
this.validator = validator;
}
public void doValidation(JoinPoint jp){
for( Object arg : jp.getArgs() ){
if (arg != null) {
Set<ConstraintViolation<Object>> violations = validator.validate(arg);
if( violations.size() > 0 ){
throw buildError(violations);
}
}
}
}
private static BadRequestException buildError( Set<ConstraintViolation<Object>> violations ){
Map<String, String> errorMap = new HashMap<String, String>();
for( ConstraintViolation error : violations ){
errorMap.put(error.getPropertyPath().toString(), error.getMessage());
}
return new BadRequestException(errorMap);
}
}
You can annotatively declare you aspect or you can do it in config (makes it reusable). As such:
<aop:config proxy-target-class="true">
<aop:aspect id="dtoValidator" ref="dtoValidator" order="10">
<aop:before method="doValidation" pointcut="execution(public * com.mycompany.client.*.*(..))"/>
</aop:aspect>
</aop:config>
Now you can have a DTO like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class LoginRequest extends AbstractDto{
#NotNull
private String userName;
#NotNull
private String password;
private LoginRequest() {
}
public LoginRequest(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
}
When it fails those #NotNull checks you will get something like this:
{
"message":"{username=must not be null",
"httpStatusCode":400,
"httpMessage":"Bad Request",
"details":{
"username":"must not be null"
}
}
Then use a RestOperation client as such
org.springframework.web.client.RestOperations restClient ...
restClient.postForObject(URL,new Dto(...),args);
Place the aspect around this restClient and you're golden (and, actually for good measure, on your service calls too).

Related

#JsonView on Spring #GetMapping query parameter objects

In Spring, is it possible to use #JsonView on URL query parameter objects? I can do it for #RequestBody but we don't have bodies in GET requests. This question is specifically for URL query parameters that have been converted to objects by Spring.
For example, I want to have a controller with this mapping:
#GetMapping("/user")
ResponseEntity<UserDTO> searchUser(#JsonView(value = UserView.Searchable.class) UserDTO userQuery) {
//Do some work here using userQuery object for searching users
return ResponseEntity.ok();
}
UserDTO:
public class UserDTO {
#JsonProperty("id")
#JsonView(UserView.Private.class)
private String id= null;
#JsonView(UserView.Searchable.class)
#JsonProperty("city")
private String city = null;
#JsonProperty("country")
#JsonView(UserView.Searchable.class)
private String country = null;
#JsonProperty("state")
private String state = null;
#JsonProperty("zipCode")
private String zipCode = null;
//More properties and getter/setters...etc
}
So if I wanted to call the endpoint I could create a URL like
localhost:8080//api/user?country=Canada to search for a user in Canada but if I tried localhost:8080//api/user?id=123, the property would be ignored.
EDIT:
I might have rushed this idea. There is no JSON de-serialization from url parameters because they are not JSON. Spring creates the query object from ServletModelAttributeMethodProcessor. Perhaps if I want some custom behavior I need to implement HandlerMethodArgumentResolver and do it myself.
EDIT 2
I'm a bit new to Spring so I have a lot to learn but I think what Ill do is just use #InitBinder to whitelist the fields for binding
#InitBinder
public void setSearchableFields(WebDataBinder binder) {
binder.setAllowedFields(
"city",
"country"
);
}
In one of my projects, I have used a POJO just for the query params. I doubt any property would be ignored by default in spring, you can have null checks to ignore.
QueryParams.java
#Data
public class QueryParams {
Integer page;
Integer pageSize;
String sortBy;
Sort.Direction direction;
String searchId;
String status;
String symbol;
public PageRequest getPageRequest(){
if(this.page==null){
this.page = 0;
}
if(this.pageSize==null){
this.pageSize = 25;
}
if(this.sortBy==null){
this.sortBy = "createdAt";
}
if(this.direction ==null){
this.direction = Sort.Direction.DESC;
}
return PageRequest.of(this.page, this.pageSize, Sort.by(this.direction, this.sortBy));
}
}
And my controller :
#GetMapping("currencies")
public ResponseEntity<Page<CurrencyConfig>> getAllCurrencies(#Valid QueryParams queryParams) {
try {
return ResponseEntity.ok(orderbookService.getAllCurrencyConfig(queryParams));
} catch (HttpClientErrorException | HttpServerErrorException e) {
throw new ResponseStatusException(e.getStatusCode(), e.getMessage());
} catch (Exception e) {
e.printStackTrace();
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
e.getLocalizedMessage());
}
}

How to test getting parameters on the Rest service using the Post method

I'm trying to test getting parameters for processing a request using the Post method
#RestController
#RequestMapping("api")
public class InnerRestController {
…
#PostMapping("createList")
public ItemListId createList(#RequestParam String strListId,
#RequestParam String strDate) {
…
return null;
}
}
test method
variant 1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class InnerRestControllerTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void innerCreatePublishList() {
String url = "http://localhost:" + this.port;
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
URI uriToEndpoint = UriComponentsBuilder
.fromHttpUrl(url)
.path(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate)
.build()
.encode()
.toUri();
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, uriToEndpoint, ItemListId.class);
}
}
variant 2
#Test
void createList() {
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate);
Map<String, String> map = new HashMap<>();
map.put("strListId", listStr);//request parameters
map.put("strDate", strDate);
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, map, ItemListId.class);
}
Update_1
In my project exceptions is handled thus:
dto
public final class ErrorResponseDto {
private String errorMsg;
private int status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
LocalDateTime timestamp;
...
handler
#RestControllerAdvice
public class ExceptionAdviceHandler {
#ExceptionHandler(value = PublishListException.class)
public ResponseEntity<ErrorResponseDto> handleGenericPublishListDublicateException(PublishListException e) {
ErrorResponseDto error = new ErrorResponseDto(e.getMessage());
error.setTimestamp(LocalDateTime.now());
error.setStatus((HttpStatus.CONFLICT.value()));
return new ResponseEntity<>(error, HttpStatus.CONFLICT);
}
}
In methods, where necessary, I throw a specific exception...
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.bind.MissingServletRequestParameterException:
Required String parameter 'strListId' is not present]
Who knows what the error is. Please explain what you need to add here and why ?
Let's take a look on declarations of postEntity:
postForEntity(URI url, Object request, Class<T> responseType)
...
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
As you can see, first argument is either URI or String with uriVariables, but second argument is always request entity.
In you first variant you put uri String as URI and then pass uriToEndpoint as request entity, pretending that it is request object. Correct solution will be:
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null, ItemListId.class);
Addressing your comments.
If server responded with HTTP 409, RestTemplate will throw exception with content of your ErrorResponseDto. You can catch RestClientResponseException and deserialize server response stored in exception. Something like this:
try {
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null,
ItemListId.class);
...
} catch(RestClientResponseException e) {
byte[] errorResponseDtoByteArray = e.getResponseBodyAsByteArray();
// Deserialize byte[] array using Jackson
}

ConstraintViolationException handler isn't executed in Micronaut

I have a ConstraintViolationException handler class that looks like this:
#Produces
#Singleton
#Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class})
public class ConstraintsViolationsExceptionHandler
implements ExceptionHandler<ConstraintViolationException, HttpResponse> {
#Override
public HttpResponse
handle(HttpRequest request, ConstraintViolationException exception) {
return HttpResponse
.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.body(new SignUpPhoneNumberErrorResponse<>(400,
"Wrong data used",
new ArrayList<>(exception.getConstraintViolations())));
}
}
where SignUpPhoneNumberErrorResponse is my error handling POJO which is getting serialized to JSON absolutely fine.
My Controller looks like this:
#Controller(PhoneAuthAndLoginConstants.CONTROLLER_BASE_PATH)
#Validated
public class UserPhoneNumberRegistrationAndLoginController {
#Inject
MongoDbUserSignUpPhoneNumberDAO mongoDbUserSignUpPhoneNumberDAO;
#Post(uri = PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_PATH,
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON)
public Single<ResponseDataEncapsulate>
signUpForPhoneVerification(#Valid #Body UserSignUpPhoneNumberEntity phoneNumber) {
return mongoDbUserSignUpPhoneNumberDAO.sendVerification(phoneNumber);
}
#Post(uri = PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_PATH
+
PhoneAuthAndLoginConstants.CONTROLLER_SIGN_UP_VERIFICATION_CODE_PARAM,
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON)
public Single<ResponseDataEncapsulate>
sendUserSignUpConfirmation(#Valid #Body UserAccountStateSignUpEntity verificationData,
HttpHeaders httpHeaders) {
return mongoDbUserSignUpPhoneNumberDAO.signUp(verificationData);
}
}
My POJO for UserAccountStateSignUpEntity looks like this:
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class UserAccountStateSignUpEntity implements UserSignUpEntity {
#NotNull #NotBlank #Size(min = 5, max = 13) private String phoneNumber;
#NotNull #NotBlank #Size(min = 7, max = 7) private String verificationCode;
#JsonIgnore private Boolean verifiedAccount = Boolean.FALSE;
public UserAccountStateSignUpEntity(String phoneNumber, String verificationCode) {
this.phoneNumber = phoneNumber;
this.verificationCode = verificationCode;
this.verifiedAccount = Boolean.TRUE;
}
#Override
public Map<String, Object> makePhoneEntityMapForMongo() {
HashMap<String, Object> returnMap = new HashMap<String, Object>() {{
put("phoneNumber", phoneNumber);
put("verificationCode", verificationCode);
put("verifiedAccount", verifiedAccount);
}};
return Collections.unmodifiableMap(returnMap);
}
}
I send in a request payload like this:
{
"phoneNumber" : "91-123456789",
"verificationCode" : "18887"
}
This should trigger a ConstraintViolationException and my handler code should execute and I should get a HTTP Forbidden. But instead I get the default HTTP Bad Request error message.
Why isn't my handler getting executed?
What can be done to make it execute?
I'm using Micronaut 1.1.3 as the web framework and the Hibernate Validator as the javax.validation implementation.
#Error that can be applied to method to map it to an error route and SignUpPhoneNumberErrorResponse would be returned as a error response body when any ConstraintViolationException occured.
For more detail visit Micronaut docs
#Controller("/${path}")
#Validated
public class UserPhoneNumberRegistrationAndLoginController {
#Post
public HttpResponse method(#Valid #Body UserAccountStateSignUpEntity verificationData, HttpHeaders httpHeaders) {
return null;
}
#Error(exception = ConstraintViolationException.class)
public SignUpPhoneNumberErrorResponse onSavedFailed(HttpRequest request, ConstraintViolationException ex) {
return new SignUpPhoneNumberErrorResponse(500,
"Wrong data used",
String.valueOf(ex.getConstraintViolations().stream().map( e -> e.getPropertyPath()+": "+e.getMessage()).collect(Collectors.toList())),
"Application",
"Error",
System.currentTimeMillis());
}
#Error(status = HttpStatus.NOT_FOUND, global = true)
public HttpResponse notFound(HttpRequest request) {
//return custom 404 error body
}
}
I recently had the same problem with error handling in Micronaut. As I figured out, there is not a org.hibernate.exception.ConstraintViolationException thrown, but a javax.persistence.PersistenceException.
For me, then it worked with #Error(exception = PersistenceException::class) (Kotlin).
#Error annotation handling for ConstraintViolationException worked for me only when it is on controller.
In the end managed to handle it like this, by replacing the micronaut ConstraintExceptionHandler bean:
#Produces
#Replaces(io.micronaut.validation.exceptions.ConstraintExceptionHandler.class)
#Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class})
public class ConstraintExceptionHandler extends io.micronaut.validation.exceptions.ConstraintExceptionHandler {
#Override
public HttpResponse handle(HttpRequest request, ConstraintViolationException exception) {
return return HttpResponse
.badRequest();
}
}
I removed the #Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class}) annotation and replaced the ArrayList<> in the body with a comma separated string and it started to work fine. Although I do not know the consequences of removing the annotation.
I believe the error here is that Micronaut already defined an exception handler for ConstraintViolationException as stated here
In order to overwrite this one I'd recommend just copying and pasting the definition from the page I linked.
#Produces
#Singleton
#Primary
#Requires(classes={javax.validation.ConstraintViolationException.class,ExceptionHandler.class})
public class ConstraintExceptionHandler
extends java.lang.Object
implements ExceptionHandler<javax.validation.ConstraintViolationException,HttpResponse<JsonError>>
(You can fill in the body. The extends java.lang.Object is not necessary)
The important part here is that I added the io.micronaut.context.annotation.Primary annotation. This will favour your handler over the other defined one. I stole this trick from ConversionErrorHandler because I was running into the same issue as you but with that handler.
Now of course you can change the HttpResponse<JsonError> to an HttpResponse<*> appropriate for your needs.

How to mock HttpServletRequest with Headers?

I am using Mockito with JUnit to test an application. I need to add headers to an HttpServletRequest while mocking. This is the first time I am using mock concept to test the application. How can we set headers to the request object while using this mock concept?
Application code
#Produces({ MediaType.APPLICATION_JSON })
#Path("/devices")
public class DvrRestService {
private static final Logger logger = LoggerFactory.getLogger(DvrRestService.class);
private DvrMiddleService dvrMiddleService;
#Inject
public DvrRestService(DvrMiddleService dvrMiddleService) {
this.dvrMiddleService = dvrMiddleService;
}
#GET
#Path("/{deviceId}/metadata")
public Response getDeviceMetadata(#Context HttpServletRequest request, #PathParam("deviceId") String deviceId,
#RequiredSession final Session session) {
try {
public static String[] REQUEST_HEADERS = { "if-none-match" };
List<String> requiredHeaders = Lists.newArrayList(REQUEST_HEADERS);
Map<String, String> headers = new HashMap<String, String>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) { // here gettting NullPointerException
String headerName = headerNames.nextElement();
if (requiredHeaders.contains(headerName.toLowerCase())) {
String value = request.getHeader(headerName);
if (value != null) {
headers.put(headerName, value);
System.out.println("headerName: " + headerName + ", Value: " + value);
}
}
}
DvrResponse response = dvrMiddleService.getDeviceMetadata(deviceId.toUpperCase(), getHeaders(request));
return processResponse(response.statusCode, response.getResponse(), DeviceMetadataResponse.class,
response.getHeaders());
} catch (Exception e) {
return processErrorResponse(e, new DeviceMetadataResponse(), logger);
}
}
}
Test
public class DvrRestServiceTest {
static DvrMiddleService dms;
static HttpServletRequest request;
static Session session;
static DvrRestService drs;
public static final String DeviceId = "000004D42070";
#BeforeClass
public static void init(){
dms = mock(DvrMiddleService.class);
request = mock(HttpServletRequest.class);
session = mock(Session.class);
drs = new DvrRestService(dms);
}
#Test
public void getDeviceMetadataTest(){
Response rs = drs.getDeviceMetadata(request, DeviceId, session);
assertEquals(Response.Status.OK, rs.getStatus());
}
}
As a starting point and demonstration for the principal you can start with the following snippet.
// define the headers you want to be returned
Map<String, String> headers = new HashMap<>();
headers.put(null, "HTTP/1.1 200 OK");
headers.put("Content-Type", "text/html");
// create an Enumeration over the header keys
Enumeration<String> headerNames = Collections.enumeration(headers.keySet());
// mock HttpServletRequest
HttpServletRequest request = mock(HttpServletRequest.class);
// mock the returned value of request.getHeaderNames()
when(request.getHeaderNames()).thenReturn(headerNames);
System.out.println("demonstrate output of request.getHeaderNames()");
while (headerNames.hasMoreElements()) {
System.out.println("header name: " + headerNames.nextElement());
}
// mock the returned value of request.getHeader(String name)
doAnswer(new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return headers.get((String) args[0]);
}
}).when(request).getHeader("Content-Type");
System.out.println("demonstrate output of request.getHeader(String name)");
String headerName = "Content-Type";
System.out.printf("header name: [%s] value: [%s]%n",
headerName, request.getHeader(headerName));
}
Output
demonstrate output of request.getHeaderNames()
header name: null
header name: Content-Type
demonstrate output of request.getHeader(String name)
header name: [Content-Type] value: [text/html]
For HttpServletRequest, I would recommend using a fully functional mock type instead of directly mocking it with Mockito mocks. The spring-test library has MockHttpServletRequest for this purpose:
#BeforeClass
public static void init(){
// ...
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addHeader("Content-Type", "text/html");
mockRequest.addHeader("if-none-match", "*");
mockRequest.addHeader("customHeader", "customValue");
this.request = mockRequest;
}
Rationale
HttpServletRequest is a complex interface with over 20 methods, with well-defined interplay between them. Using a fully functional mock type for HttpServletRequest from a library simplifies the mocking, removing the need to carefully mock out the methods you're using.
One advantage of this approach is that it is more resilient in the face of future refactorings that get the same information using other methods on the class. In the case of retrieving the "if-none-match" header in HttpServletRequest, I see three different methods that could legitimately be used to retrieve the header: getHeader(String name), getHeaders(String name), and getHeaderNames(). Furthermore, the argument for both getHeader and getHeaders are case-insensitive (the same results are returned for "if-none-match", "If-None-Match", etc.), so any possible argument casing would be correct. It's very possible to support this with a direct mock, though it involves extra boilerplate code that complicates the test and makes it less obvious.
The MockHttpServletRequest class from the spring-test library mocks this interface, and allows setting the header and other values via a straightforward API. While the library is designed for testing Spring applications, the MockHttpServletRequest class is independent of any Spring-specific functionality, and should be completely usable on its own even if the application doesn't use Spring.
This worked in my case
mockMvc.perform(post("<<url>>").content("<<jsonStrig>>").header("key", "value"));
can also be used in get request.
I know the OP is using Mockito. This answer is for those using spock. You can accomplish this pretty easily.
class MyTestSpec extends Specification {
HttpServletRequest request = Mock()
MyTestClass myTestClass = new MyTestClass()
def 'my test'() {
setup:
String expectedHeader = "x-mycustom-header"
when:
String someResult = myTestClass.someTestMethod()
then:
// here is where you return your header from the mocked request
1 * request.getHeader(_ as String) >> expectedHeader
}
}

Custom http code with Spring MVC

I use the following code to handle rest calls using Spring MVC.
#RequestMapping(value = "login", method = RequestMethod.GET)
public #ResponseBody
User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
...
return user;
}
I would like to send the client customer http codes for wrong username, wrong passwords, password changed and password expire conditions. How can I modify the existing code to send these error codes to the client?
You can use controller advice to map exception thrown within controller to some client specific data at runtime.
For example if user is not found, your controller should throw some exception (custom or existed one)
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UserNotFoundException(username); //or another exception, it's up to you
return user;
}
}
Then you should add #ControllerAdvice that will catch controller exceptions and make 'exception-to-status' mapping (pros: you will have single point of responsibility for 'exception-to-status-mapping'):
#ControllerAdvice
public class SomeExceptionResolver {
#ExceptionHandler(Exception.class)
public void resolveAndWriteException(Exception exception, HttpServletResponse response) throws IOException {
int status = ...; //you should resolve status here
response.setStatus(status); //provide resolved status to response
//set additional response properties like 'content-type', 'character encoding' etc.
//write additional error message (if needed) to response body
//for example IOUtils.write("some error message", response.getOutputStream());
}
}
Hope this helps.
One way is to add some additional classes for returning HTTP error. Your code will looks like this:
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UnauthorizedException();
return user;
}
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException{
}
In this case user will get 401 response status code
I hope it helps
You can return an HTTP 500 or code of your choosing (from the org.springframework.http.HttpStatus enumeration) and use a custom error to emulate something like a SOAP fault within the JSON response.
For example:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(YourTargetException.class)
#ResponseBody
Fault caughtYourTargetException(HttpServletRequest req, Exception ex) {
String code = ex.getClass().getName();
String reason = "Caught YourTargetException."
return new Fault(code, reason);
}
The Fault class could look something like this (inspired by http://www.w3.org/TR/soap12-part1/#soapfault):
/**
* A Fault is an object that can be serialized as JSON when expected errors occur.
*/
public class Fault {
#JsonProperty("faultCode")
private final String code;
#JsonProperty("faultReason")
private final String reason;
#JsonProperty("faultDetails")
private final List<String> details = new ArrayList<>();
public Fault(String code, String reason) {
this.code = code;
this.reason = reason;
}
public Fault(String code, String reason, String... detailEntries) {
this.code = code;
this.reason = reason;
details.addAll(Arrays.asList(detailEntries));
}
public String getCode() {
return code;
}
public String getReason() {
return reason;
}
/**
* Zero or more details may be associated with the fault. It carries
* additional information relative to the fault. For example, the Detail
* element information item might contain information about a message
* not containing the proper credentials, a timeout, etc.
* #return Zero or more detail entries.
*/
public Iterable<String> getDetails() {
return details;
}
#Override
public String toString() {
return String.format("Fault %s occurred. The reason is %s.", getCode(),
getReason());
}
}
You could use one of the existing SOAPFaults in Java frameworks, but I have found they don't play well in REST. Creating my own simple version turned out to be simpler.
You can define your own status code and returning objects. In your code throw custom exceptions and then define an exception handler as follows:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyRetObject> handleControllerError(HttpServletRequest req, MyException ex) {
LOG.warn("My error", ex);
MyRetObject errorMessage = new MyRetObject(ex.getMessage());
return ResponseEntity.status(600).body(errorMessage);
}
}
In your case replace MyExeption.class by UserNotFoundException.class and build your customer error response object and error code

Categories