Skip to content

Commit d0d0406

Browse files
committed
custom error message
1 parent 24de497 commit d0d0406

File tree

7 files changed

+327
-3
lines changed

7 files changed

+327
-3
lines changed

spring-security-rest/pom.xml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,26 @@
139139
</dependency>
140140

141141
<!-- test scoped -->
142-
142+
<dependency>
143+
<groupId>org.springframework</groupId>
144+
<artifactId>spring-test</artifactId>
145+
<version>${org.springframework.version}</version>
146+
<scope>test</scope>
147+
</dependency>
148+
149+
<dependency>
150+
<groupId>com.jayway.restassured</groupId>
151+
<artifactId>rest-assured</artifactId>
152+
<version>${rest-assured.version}</version>
153+
<scope>test</scope>
154+
<exclusions>
155+
<exclusion>
156+
<artifactId>commons-logging</artifactId>
157+
<groupId>commons-logging</groupId>
158+
</exclusion>
159+
</exclusions>
160+
</dependency>
161+
143162
<dependency>
144163
<groupId>junit</groupId>
145164
<artifactId>junit</artifactId>
@@ -276,6 +295,7 @@
276295
<org.hamcrest.version>1.3</org.hamcrest.version>
277296
<junit.version>4.12</junit.version>
278297
<mockito.version>1.10.19</mockito.version>
298+
<rest-assured.version>2.4.1</rest-assured.version>
279299

280300
<!-- swagger -->
281301
<springfox-swagger.version>2.2.2</springfox-swagger.version>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.baeldung.web;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
6+
import org.springframework.http.HttpStatus;
7+
8+
public class ApiError {
9+
10+
private HttpStatus status;
11+
private String message;
12+
private List<String> errors;
13+
14+
//
15+
16+
public ApiError() {
17+
super();
18+
}
19+
20+
public ApiError(final HttpStatus status, final String message, final List<String> errors) {
21+
super();
22+
this.status = status;
23+
this.message = message;
24+
this.errors = errors;
25+
}
26+
27+
public ApiError(final HttpStatus status, final String message, final String error) {
28+
super();
29+
this.status = status;
30+
this.message = message;
31+
errors = Arrays.asList(error);
32+
}
33+
34+
//
35+
36+
public HttpStatus getStatus() {
37+
return status;
38+
}
39+
40+
public void setStatus(final HttpStatus status) {
41+
this.status = status;
42+
}
43+
44+
public String getMessage() {
45+
return message;
46+
}
47+
48+
public void setMessage(final String message) {
49+
this.message = message;
50+
}
51+
public List<String> getErrors() {
52+
return errors;
53+
}
54+
55+
public void setErrors(final List<String> errors) {
56+
this.errors = errors;
57+
}
58+
59+
public void setError(final String error) {
60+
errors = Arrays.asList(error);
61+
}
62+
63+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package org.baeldung.web;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.springframework.beans.TypeMismatchException;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.validation.BindException;
11+
import org.springframework.validation.FieldError;
12+
import org.springframework.validation.ObjectError;
13+
import org.springframework.web.HttpMediaTypeNotSupportedException;
14+
import org.springframework.web.HttpRequestMethodNotSupportedException;
15+
import org.springframework.web.bind.MethodArgumentNotValidException;
16+
import org.springframework.web.bind.MissingServletRequestParameterException;
17+
import org.springframework.web.bind.annotation.ControllerAdvice;
18+
import org.springframework.web.bind.annotation.ExceptionHandler;
19+
import org.springframework.web.context.request.WebRequest;
20+
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
21+
import org.springframework.web.multipart.support.MissingServletRequestPartException;
22+
import org.springframework.web.servlet.NoHandlerFoundException;
23+
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
24+
25+
@ControllerAdvice
26+
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
27+
28+
// 400
29+
30+
@Override
31+
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
32+
logger.info(ex.getClass().getName());
33+
//
34+
final List<String> errors = new ArrayList<String>();
35+
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
36+
errors.add(error.getField() + ": " + error.getDefaultMessage());
37+
}
38+
for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
39+
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
40+
}
41+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
42+
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
43+
}
44+
45+
@Override
46+
protected ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
47+
logger.info(ex.getClass().getName());
48+
//
49+
final List<String> errors = new ArrayList<String>();
50+
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
51+
errors.add(error.getField() + ": " + error.getDefaultMessage());
52+
}
53+
for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
54+
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
55+
}
56+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
57+
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
58+
}
59+
60+
@Override
61+
protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
62+
logger.info(ex.getClass().getName());
63+
//
64+
final String error = ex.getValue() + " value for " + ex.getPropertyName() + " should be of type " + ex.getRequiredType();
65+
66+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
67+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
68+
}
69+
70+
@ExceptionHandler({ MethodArgumentTypeMismatchException.class })
71+
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(final MethodArgumentTypeMismatchException ex, final WebRequest request) {
72+
logger.info(ex.getClass().getName());
73+
//
74+
final String error = ex.getName() + " should be of type " + ex.getRequiredType().getName();
75+
76+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
77+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
78+
}
79+
80+
@Override
81+
protected ResponseEntity<Object> handleMissingServletRequestPart(final MissingServletRequestPartException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
82+
logger.info(ex.getClass().getName());
83+
//
84+
final String error = ex.getRequestPartName() + " part is missing";
85+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
86+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
87+
}
88+
89+
@Override
90+
protected ResponseEntity<Object> handleMissingServletRequestParameter(final MissingServletRequestParameterException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
91+
logger.info(ex.getClass().getName());
92+
//
93+
final String error = ex.getParameterName() + " parameter is missing";
94+
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
95+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
96+
}
97+
98+
// 404
99+
100+
@Override
101+
protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
102+
logger.info(ex.getClass().getName());
103+
//
104+
final String error = "No handler found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
105+
106+
final ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), error);
107+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
108+
}
109+
110+
// 405
111+
112+
@Override
113+
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(final HttpRequestMethodNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
114+
logger.info(ex.getClass().getName());
115+
//
116+
final StringBuilder builder = new StringBuilder();
117+
builder.append(ex.getMethod());
118+
builder.append(" method is not supported for this request. Supported methods are ");
119+
ex.getSupportedMethods();
120+
121+
final ApiError apiError = new ApiError(HttpStatus.METHOD_NOT_ALLOWED, ex.getLocalizedMessage(), builder.toString());
122+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
123+
}
124+
125+
// 415
126+
127+
@Override
128+
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(final HttpMediaTypeNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
129+
logger.info(ex.getClass().getName());
130+
//
131+
final StringBuilder builder = new StringBuilder();
132+
builder.append(ex.getContentType());
133+
builder.append(" media type is not supported. Supported media types are ");
134+
ex.getSupportedMediaTypes().forEach(t -> builder.append(t + ", "));
135+
136+
final ApiError apiError = new ApiError(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getLocalizedMessage(), builder.substring(0, builder.length() - 2));
137+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
138+
}
139+
140+
141+
// 500
142+
143+
@ExceptionHandler({ Exception.class })
144+
public ResponseEntity<Object> handleAll(final Exception ex, final WebRequest request) {
145+
logger.info(ex.getClass().getName());
146+
//
147+
final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
148+
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
149+
}
150+
151+
}
152+

spring-security-rest/src/main/java/org/baeldung/web/controller/FooController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
import org.baeldung.persistence.model.Foo;
1010
import org.springframework.beans.factory.annotation.Autowired;
1111
import org.springframework.context.ApplicationEventPublisher;
12+
import org.springframework.http.HttpStatus;
1213
import org.springframework.stereotype.Controller;
1314
import org.springframework.web.bind.annotation.PathVariable;
15+
import org.springframework.web.bind.annotation.RequestBody;
1416
import org.springframework.web.bind.annotation.RequestMapping;
1517
import org.springframework.web.bind.annotation.RequestMethod;
1618
import org.springframework.web.bind.annotation.ResponseBody;
19+
import org.springframework.web.bind.annotation.ResponseStatus;
1720
import org.springframework.web.util.UriComponentsBuilder;
1821

1922
import com.google.common.collect.Lists;
@@ -47,4 +50,11 @@ public List<Foo> findAll() {
4750
return Lists.newArrayList(new Foo(randomAlphabetic(6)));
4851
}
4952

53+
// write - just for test
54+
@RequestMapping(method = RequestMethod.POST)
55+
@ResponseStatus(HttpStatus.CREATED)
56+
@ResponseBody
57+
public Foo create(@RequestBody final Foo foo) {
58+
return foo;
59+
}
5060
}

spring-security-rest/src/main/webapp/WEB-INF/web.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
<!-- Spring child -->
2727
<servlet>
2828
<servlet-name>api</servlet-name>
29-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
30-
<load-on-startup>1</load-on-startup>
29+
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
30+
<init-param>
31+
<param-name>throwExceptionIfNoHandlerFound</param-name>
32+
<param-value>true</param-value>
33+
</init-param>
3134
</servlet>
3235
<servlet-mapping>
3336
<servlet-name>api</servlet-name>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.baeldung.web;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.test.context.ContextConfiguration;
10+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11+
import org.springframework.test.context.support.AnnotationConfigContextLoader;
12+
13+
import com.jayway.restassured.RestAssured;
14+
import com.jayway.restassured.authentication.FormAuthConfig;
15+
import com.jayway.restassured.response.Response;
16+
import com.jayway.restassured.specification.RequestSpecification;
17+
18+
@RunWith(SpringJUnit4ClassRunner.class)
19+
@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class)
20+
public class FooLiveTest {
21+
private static final String URL_PREFIX = "http://localhost:8080/spring-security-rest";
22+
private FormAuthConfig formConfig = new FormAuthConfig(URL_PREFIX + "/login", "username", "password");
23+
24+
private RequestSpecification givenAuth() {
25+
return RestAssured.given().auth().form("user", "userPass", formConfig);
26+
}
27+
28+
@Test
29+
public void whenMethodArgumentMismatch_thenBadRequest() {
30+
final Response response = givenAuth().get(URL_PREFIX + "/api/foos/ccc");
31+
final ApiError error = response.as(ApiError.class);
32+
assertEquals(HttpStatus.BAD_REQUEST, error.getStatus());
33+
assertEquals(1, error.getErrors().size());
34+
assertTrue(error.getErrors().get(0).contains("should be of type"));
35+
36+
}
37+
38+
@Test
39+
public void whenNoHandlerForHttpRequest_thenNotFound() {
40+
final Response response = givenAuth().delete(URL_PREFIX + "/api/xx");
41+
final ApiError error = response.as(ApiError.class);
42+
assertEquals(HttpStatus.NOT_FOUND, error.getStatus());
43+
assertEquals(1, error.getErrors().size());
44+
assertTrue(error.getErrors().get(0).contains("No handler found"));
45+
}
46+
47+
@Test
48+
public void whenHttpRequestMethodNotSupported_thenMethodNotAllowed() {
49+
final Response response = givenAuth().delete(URL_PREFIX + "/api/foos/1");
50+
final ApiError error = response.as(ApiError.class);
51+
assertEquals(HttpStatus.METHOD_NOT_ALLOWED, error.getStatus());
52+
assertEquals(1, error.getErrors().size());
53+
assertTrue(error.getErrors().get(0).contains("Supported methods are"));
54+
}
55+
56+
@Test
57+
public void whenSendInvalidHttpMediaType_thenUnsupportedMediaType() {
58+
final Response response = givenAuth().body("").post(URL_PREFIX + "/api/foos");
59+
final ApiError error = response.as(ApiError.class);
60+
assertEquals(HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus());
61+
assertEquals(1, error.getErrors().size());
62+
assertTrue(error.getErrors().get(0).contains("media type is not supported"));
63+
}
64+
65+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.baeldung.web;
2+
3+
import org.springframework.context.annotation.ComponentScan;
4+
import org.springframework.context.annotation.Configuration;
5+
6+
@Configuration
7+
@ComponentScan({ "org.baeldung.web" })
8+
public class TestConfig {
9+
10+
11+
}

0 commit comments

Comments
 (0)