I'm trying to override default error messages in BindingResult. I implemented Controller advice like this
#ControllerAdvice
public final class DefaultControllerAdvice {
private static final String ERROR_MESSAGE_FORMATTER = " - ";
#ExceptionHandler(BindException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public List<String> handleValidationException(final BindException exception) {
return exception.getBindingResult()
.getAllErrors()
.stream()
.filter(error -> error instanceof FieldError)
.map(objectError -> (FieldError) objectError)
.map(errorMessageFormatter)
.collect(Collectors.toList());
}
private final Function<FieldError, String> errorMessageFormatter =
error -> error.getField() + ERROR_MESSAGE_FORMATTER + error.getDefaultMessage();
}
and my controller
#PostMapping(value = "/register")
private String postRegistration( #ModelAttribute #Valid final UserCreateFormDto user, final BindingResult result,
final RedirectAttributes redirectAttributes, final WebRequest webRequest) {
try {
if (result.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
redirectAttributes.addFlashAttribute("user", user);
throw new BindException(result);
}
if (userService.checkEmailExist(user.getEmail())) {
throw new UserNotExistsException("User with email: "+ user.getEmail()+" already exists.");
}
final User registered = userService.createNewUserAccount(user);
final String appUrl = webRequest.getContextPath();
eventPublisher.publishEvent(
new RegistrationCompleteEvent(registered, webRequest.getLocale(), appUrl));
return "redirect:/login?success";
} catch (UserNotExistsException error) {
return "redirect:/register?exists";
} catch (BindException error) {
return "redirect:/register";
}
}
and test case
#Test
public void shouldNotCreateUserWhenUsernameIsEmpty() throws Exception {
//given
final UserCreateFormDto userCreateFormDto = createUserCreateForm();
final User user = createUser();
given(userService.checkEmailExist(userCreateFormDto.getEmail())).willReturn(false);
given(userService.createNewUserAccount(any(UserCreateFormDto.class))).willReturn(user);
//when
final MvcResult response = mockMvc
.perform(post("/register").with(csrf())
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("username","")
.param("email",userCreateFormDto.getEmail())
.param("password", Arrays.toString(userCreateFormDto.getPassword()))
.param("matchingPassword", Arrays.toString(userCreateFormDto.getMatchingPassword())))
.andReturn();
//then
assertThat(response.getFlashMap().isEmpty()).isFalse();
assertThat(response.getResponse().getStatus()).isEqualTo(HttpStatus.FOUND.value());
assertThat(response.getResponse().getRedirectedUrl()).isEqualTo("/register");
verify(userService, times(1)).checkEmailExist(userCreateFormDto.getEmail());
verify(userService, times(0)).createNewUserAccount(any(UserCreateFormDto.class));
my question is how to get the bindingResult default error message?
I would like to test what an error message is getting while validate input fields.
Related
I would like to simplify my code and merge common parts of the several methods into new one. Could you give me the piece of advice - what is the best way to do it?
My methods are:
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(BAD_REQUEST.toString());
errorDto.setScimType("prohibited");
return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(value = {UserAlreadyExistsException.class})
public ResponseEntity<ErrorDto> userNameExistsConflict(final UserAlreadyExistsException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(CONFLICT.toString());
errorDto.setScimType("uniqueness");
return new ResponseEntity<>(errorDto, HttpStatus.CONFLICT);
}
#ExceptionHandler(value = {UserNotFoundException.class})
public ResponseEntity<ErrorDto> userNameNotFoundConflict(final UserNotFoundException exception) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(NOT_FOUND.toString());
errorDto.setScimType("prohibited");
return new ResponseEntity<>(errorDto, HttpStatus.NOT_FOUND);
}
I would like to separate the common part which is:
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(MEHTOD.toString());
errorDto.setScimType("something");
can you extract the common part to a method like this?
private ResponseEntity<ErrorDto> conflict(final Throwable exception, HttpStatus status, String scrimType) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(status.toString());
errorDto.setScimType(scrimType);
return new ResponseEntity<>(errorDto, status);
}
and call it from your methods,
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
return conflict(exception, HttpStatus.BAD_REQUEST, "prohibited");
}
#ExceptionHandler(value = {UserAlreadyExistsException.class})
public ResponseEntity<ErrorDto> userNameExistsConflict(final UserAlreadyExistsException exception) {
return conflict(exception, HttpStatus.CONFLICT, "uniqueness");
}
#ExceptionHandler(value = {UserNotFoundException.class})
public ResponseEntity<ErrorDto> userNameNotFoundConflict(final UserNotFoundException exception) {
return conflict(exception, HttpStatus.NOT_FOUND, "prohibited");
}
Create a method:
private ResponseEntity<ErrorDto> conflict(final Throwable exception, final Object status, final String scimType, final HttpStatus httpStatus) {
final var errorDto = new ErrorDto();
errorDto.setDetail(exception.getMessage());
errorDto.setStatus(status.toString());
errorDto.setScimType(scimType);
return new ResponseEntity<>(errorDto, httpStatus);
}
and use it like
#ExceptionHandler(value = {ProhibitedScimTypeException.class})
public ResponseEntity<ErrorDto> policyConflict(final ProhibitedScimTypeException exception) {
return this.conflict(exception, BAD_REQUEST, "prohibited", HttpStatus.BAD_REQUEST);
}
I'm creating a component class that overrides a reactive method that calls another microservice "uaa" that validates a token, but when I verify that the token is invalid I throw an exception, but that exception does not catch in my exception controller handler
here is my component class
#Slf4j
#Component
#RequiredArgsConstructor
public class AuthFilter implements GlobalFilter {
private final JwtTokenProviderService jwtTokenProviderService;
private final TokenStatusDaoService tokenStatusDaoService;
private final WebClient webClient;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("something in the way");
List<String> headers = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
if(CollectionUtils.isEmpty(headers)) {
log.trace("Request came without token");
return chain.filter(exchange);
} else {
String authToken = headers.get(0);
log.trace("Request holds a token");
log.debug("Check if token has expired ...");
if(jwtTokenProviderService.isTokenExpired(authToken)) {
log.debug("Token has expired will throw an error");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_HAS_EXPIRED, "Token has expired");
}else {
log.debug("Check if token is valid and already saved");
String userId = jwtTokenProviderService.getClaimsFromToken(authToken).get(SecurityUtils.IDENTIFIER_KEY).toString();
if(!tokenStatusDaoService.exists(TokenStatusSpecification.withToken(authToken).and(TokenStatusSpecification.withUserId(Long.parseLong(userId))))) {
return webClient.get()
.uri("http://uaa", uriBuilder -> uriBuilder
.path("/validate-token")
.queryParam("token", authToken).build()).retrieve()
.bodyToMono(TokenValidationGetResource.class)
.map(tokenValidationGetResource -> {
if (!tokenValidationGetResource.isValid()) {
log.debug("token is not valid");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_NOT_VALID, "Token is not valid");
} else {
log.debug("token is valid");
TokenStatusEntity tokenStatusEntity;
try {
tokenStatusEntity = tokenStatusDaoService.findOne(TokenStatusSpecification.withUserId(Long.parseLong(userId)));
} catch (Exception e) {
log.debug("No token defined for user: {}. Will save a new one ...", userId);
tokenStatusEntity = new TokenStatusEntity();
}
tokenStatusEntity.setToken(authToken);
tokenStatusEntity.setUserId(Long.parseLong(userId));
tokenStatusEntity.setStatus(TokenStatusEnum.VALID);
tokenStatusDaoService.save(tokenStatusEntity);
log.debug("Token status entity: {}", tokenStatusEntity);
return exchange;
}
}).flatMap(chain::filter);
} else {
log.debug("Token exists in DB");
return chain.filter(exchange);
}
}
}
}
}
and here is my exception controller handler:
#ControllerAdvice
public class ExceptionControllerImpl implements ExceptionController {
#Override
#ExceptionHandler({
AuthorizationForbiddenException.class
})
public ResponseEntity<ErrorDetailResource> handleGenericExceptions(
AbstractBaseException e, HttpServletRequest request) {
ErrorDetailResource errorDetailResource = new ErrorDetailResource();
errorDetailResource.setTimestamp(Instant.now().toEpochMilli());
errorDetailResource.setTitle(e.getTitle().toString());
errorDetailResource.setCode(e.getTitle().getCode());
errorDetailResource.setDeveloperMessage(e.getClass().getName());
errorDetailResource.setStatus(e.getStatus().value());
errorDetailResource.setDetail(e.getMessage());
return new ResponseEntity<>(errorDetailResource, e.getStatus());
}
}
Hello Those exceptions are thrown on a mono method in a reactive manner, so they can not be caught by controller advice, instead of doing that create a class which will extends the abstract class AbstractErrorWebExceptionHandler
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes globalErrorAttributes,
ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(globalErrorAttributes, new WebProperties.Resources(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());
Throwable error = null;
// here is your abstract base exception
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
HttpStatus statusCode = baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
return ServerResponse.status(statusCode)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
And of course do not forget to add DefaultErrorAttributes
#Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Throwable error = null;
// here is your abstract base exception
// cast the error to your exception class
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
Map<String, Object> errorResources = new HashMap<>();
// Define the attribute that you want to return in response body
errorResources.put("attribute1", Instant.now().toEpochMilli());
errorResources.put("attribute2", baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR);
return errorResources;
}
}
I have added customexception class to handle my api class.
Exception class
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorMessage error = new ErrorMessage("Server Error", details);
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(RecordNotFoundException.class)
public final ResponseEntity<Object> handleConfigNotFoundException(RecordNotFoundException ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorMessage error = new ErrorMessage("Record Not Found", details);
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for (ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorMessage error = new ErrorMessage("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
API method
#RequestMapping(value = "/config", params = { "appCode", "appVersion" }, method = RequestMethod.GET)
public ResponseEntity<ConfigResponse> getConfig(#RequestParam(value = "appCode", required = true) String appCode,
#RequestParam(value = "appVersion", required = true) String appVersion) {
List<AppConfig> result = configRepository.findByCodeAndVersion(appCode, appVersion);
if (result.isEmpty()) {
throw new RecordNotFoundException(appCode + " or " + appVersion + "does not exist");
}
AppConfig config = new AppConfig();
return new ResponseEntity<ConfigResponse>(config.convertToResponse(result), HttpStatus.OK);
}
Record Not found
#ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException {
public RecordNotFoundException(String exception) {
super(exception);
}
}
I am checking for the record not found but it still gives me the spring exception and not my custom exception.
I have a controller that looks like:
#PostMapping(path = "/email", consumes = "application/json", produces = "application/json")
public String notification(#RequestBody EmailNotificationRequest emailNotificationRequest) throws IOException {
String jobId = emailNotificationRequest.getJobId();
try {
service.jobId(jobId);
return jobId;
} catch (ApplicationException e) {
return "failed to send email to for jobId: " + jobId;
}
}
And I am trying to test the controller but getting a 400 back:
#Before
public void setUp() {
this.mvc = MockMvcBuilders.standaloneSetup(emailNotificationController).build();
}
#Test
public void successfulServiceCallShouldReturn200() throws Exception {
String request = "{\"jobId\" : \"testId\"}";
MvcResult result = mvc.perform(post("/email")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(request))
.andReturn();
String content = result.getResponse().getContentAsString();
assertThat(content, isNotNull());
}
Now I realize that 400 means the request is bad. So I tried making my own request and then turning it to a JSON string like so:
#Test
public void successfulServiceCallShouldReturn200() throws Exception {
EmailNotificationRequest emailNotificationRequest = new emailNotificationRequest();
emailNotificationRequest.setJobId("testJobId");
MvcResult result = mvc.perform(post("/notification/email")
.content(asJsonString(emailNotificationRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
assertThat(result, isNotNull());
}
public static String asJsonString(final Object obj) {
try {
final ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
final String jsonContent = mapper.writeValueAsString(obj);
return jsonContent;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
I think it has something to do with the content() since i am getting 400 and that has to do with the actual request. Can someone please tell me why the request is still bad here? Or a better way to test this particular POST method? Thanks in advance.
You have to add accept("application/json")).
If not the mock does not accept this content type.
Getting following exception while hitting a Rest endpoint. How do I typecast from String to ProtectPanReplyType class?
Error:
Error - Request: http://localhost:9090/hosted-payments-webapp-1.0.0/pan/protect
raised java.lang.ClassCastException: com.gsicommerce.api.checkout.ProtectPanReplyType cannot be cast to java.lang.String
ProtectPanServiceImpl.java
#Service
public class ProtectPanServiceImpl implements ProtectPanService {
#Override
public ResponseEntity<?> sendProtectPanRequest(ProtectPan protectPan) {
String pan = protectPan.getPaymentAccountNumber();
String tenderClass = protectPan.getTenderClass();
String protectPanRequest = XMLHelper.createProtectPanRequest(pan, tenderClass);
System.out.println("protectPanRequest = " + protectPanRequest);
ResponseEntity<String> response = null;
try {
response = ApiClientUtils.callClientByEndpointandMessage(protectPanRequest, DEV_PUBLIC_API_URL,
ProtectPanReplyType.class);
System.out.println("response.getClass() = " + response.getClass());
//DOES NOT WORK
//ProtectPanReplyType protectPanReplyType = (ProtectPanReplyType)response.getBody();
//THROWS ClassCastException HERE
System.out.println(response.getBody());
} catch (JiBXException e) {
e.printStackTrace();
}
return response;
}
}
ApiClientUtils.java
public ResponseEntity<String> callClientByEndpointandMessage(String xmlRequest, String endpoint, Class<?> replyType) throws JiBXException {
HttpEntity<String> request = createRequestForUser("username", "secret",xmlRequest);
ResponseEntity<String> response = restOperations.postForEntity(endpoint, request, String.class);
ResponseEntity formattedResponse = new ResponseEntity(null, HttpStatus.BAD_REQUEST);
try {
Object jibxObject = JibxHelper.unmarshalMessage(response.getBody(), replyType);
formattedResponse = new ResponseEntity(jibxObject, HttpStatus.OK);
} catch (JiBXException e) {
FaultResponseType faultResponse = JibxHelper.unmarshalMessage(response.getBody(), FaultResponseType.class);
formattedResponse = new ResponseEntity(faultResponse, HttpStatus.BAD_REQUEST);
}
return formattedResponse;
}
ProtectPan.java
public class ProtectPan {
#JsonProperty("paymentAccountNumber")
private String paymentAccountNumber;
#JsonProperty("tenderClass")
private String tenderClass;
public String getPaymentAccountNumber() {
return paymentAccountNumber;
}
public String getTenderClass() {
return tenderClass;
}
}
ProtectPanReplyType.java
public class ProtectPanReplyType {
private String token;
private List<Element> anyList = new ArrayList<Element>();
private String sessionId;
//getters and setter removed for brevity
}
Use ResponseEntity<ProtectPanReplyType> instead ResponseEntity<String>
Build and Return ProtectPanReplyType from your restOperations.postForEntity()
Was finally able to get the object after making following changes.
ApiClientUtils.java
public ResponseEntity<?> callClientByEndpointandMessage(String xmlRequest, String endpoint, Class<?> replyType) throws JiBXException {
HttpEntity<String> request = createRequestForUser("payment", "SONitc2m8y", xmlRequest);
ResponseEntity<String> response = restOperations.postForEntity(endpoint, request, String.class);
ResponseEntity<?> formattedResponse = null;
try {
Object jibxObject = JibxHelper.unmarshalMessage(response.getBody(), replyType);
formattedResponse = new ResponseEntity(jibxObject, HttpStatus.OK);
} catch (JiBXException e) {
FaultResponseType faultResponse = JibxHelper.unmarshalMessage(response.getBody(), FaultResponseType.class);
formattedResponse = new ResponseEntity(faultResponse, HttpStatus.BAD_REQUEST);
}
return formattedResponse;
}
ProtectPanServiceImpl.java
#Override
public ResponseEntity<?> sendProtectPanRequest(ProtectPan protectPan) {
String pan = protectPan.getPaymentAccountNumber();
String tenderClass = protectPan.getTenderClass();
String protectPanRequest = XMLHelper.createProtectPanRequest(pan, tenderClass);
ResponseEntity<?> response = null;
try {
response = publicApiClientUtils.callClientByEndpointandMessage(protectPanRequest, DEV_PUBLIC_API_URL, ProtectPanReplyType.class);
ProtectPanReplyType protectPanReplyType = (ProtectPanReplyType) response.getBody();
System.out.println("protectPanReplyType = " + protectPanReplyType);
} catch (JiBXException e) {
e.printStackTrace();
}
return response;
}