Unable to get rid of 'Exception while fetching data(/{apiName})' in graphql-spqr-spring-boot-starter - exception

I’m using ‘graphql-spqr-spring-boot-starter’ library version 0.0.4 of ‘io.leangen.graphql’. I'm able to customize errors. See the below code and screenshot for reference:
Models:
#Getter
#Setter
#ToString
#Entity
#Accessors
public class Student {
#Id
#GraphQLQuery(name = "id", description = "A Student's ID")
private Long id;
#GraphQLQuery(name = "name", description = "A student's name")
private String name;
private String addr;
}
Service class:
#Service
#GraphQLApi
public class StudentService{
private final StudentRepository studentRepository;
private final AddressRepository addressRepository;
public StudentService(StudentRepository studentRepository, AddressRepository addressRepository) {
this.addressRepository = addressRepository;
this.studentRepository = studentRepository;
}
#GraphQLQuery(name = "allStudents")
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
#GraphQLQuery(name = "student")
public Optional<Student> getStudentById(#GraphQLArgument(name = "id") Long id) {
if(studentRepository.findById(id) != null)
return studentRepository.findById(id);
throw new StudentNotFoundException("We were unable to find a student with the provided id", "id");
}
#GraphQLMutation(name = "saveStudent")
public Student saveStudent(#GraphQLArgument(name = "student") Student student) {
if(student.getId() == null)
throw new NoIdException("Please provide an Id to create a Student entry.");
return studentRepository.save(student);
}
}
Customized Exception class:
import java.util.List;
import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
public class NoIdException extends RuntimeException implements GraphQLError {
private String noIdMsg;
public NoIdException(String noIdMsg) {
this.noIdMsg = noIdMsg;
}
#Override
public List<SourceLocation> getLocations() {
// TODO Auto-generated method stub
return null;
}
#Override
public ErrorType getErrorType() {
// TODO Auto-generated method stub
return ErrorType.ValidationError;
}
#Override
public String getMessage() {
// TODO Auto-generated method stub
return noIdMsg;
}
}
However, I’m not sure how to get rid of Exception while fetching data (/saveStudent) as seen on the above screenshot for the message field. I know we can have GraphQLExceptionHandler class which implements GraphQLErrorHandler (graphql-java-kickstart). But what is the option for sqpr-spring-boot-starter?
import graphql.*;
import graphql.kickstart.execution.error.*;
import org.springframework.stereotype.*;
import java.util.*;
import java.util.stream.*;
#Component
public class GraphQLExceptionHandler implements GraphQLErrorHandler {
#Override
public List<GraphQLError> processErrors(List<GraphQLError> list) {
return list.stream().map(this::getNested).collect(Collectors.toList());
}
private GraphQLError getNested(GraphQLError error) {
if (error instanceof ExceptionWhileDataFetching) {
ExceptionWhileDataFetching exceptionError = (ExceptionWhileDataFetching) error;
if (exceptionError.getException() instanceof GraphQLError) {
return (GraphQLError) exceptionError.getException();
}
}
return error;
}
}
Could someone please help me how can I remove this statement and send just the specific message?

You can create a Bean and override DataFetcherExceptionHandler. To override it, you have to override the execution strategy too:
#Bean
public GraphQL graphQL(GraphQLSchema schema) {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(new AsyncExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.mutationExecutionStrategy(new AsyncSerialExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.build();
}
private static class CustomDataFetcherExceptionHandler implements DataFetcherExceptionHandler {
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
Throwable exception = handlerParameters.getException();
SourceLocation sourceLocation = handlerParameters.getSourceLocation();
CustomExceptionWhileDataFetching error = new CustomExceptionWhileDataFetching(exception, sourceLocation);
return DataFetcherExceptionHandlerResult.newResult().error(error).build();
}
}
private static class CustomExceptionWhileDataFetching implements GraphQLError {
private final String message;
private final List<SourceLocation> locations;
public CustomExceptionWhileDataFetching(Throwable exception, SourceLocation sourceLocation) {
this.locations = Collections.singletonList(sourceLocation);
this.message = exception.getMessage();
}
#Override
public String getMessage() {
return this.message;
}
#Override
public List<SourceLocation> getLocations() {
return this.locations;
}
#Override
public ErrorClassification getErrorType() {
return ErrorType.DataFetchingException;
}
}

Related

How can I read a JSON with header and body with spring boot

Good morning I have a small query, I am doing a small web service rest with spring boot, the issue is that it is working fine and everything else, as I am doing as follows, receives a parameter and returns a response based on a Stored Procedue in the database:
But now I have changed the request, and it is including header and body, like the following:
{
"ValidateClient": {
"Header": {
"country": "VE",
"lang": "ES",
"entity": "TMVE",
"system": "76",
"subsystem": "APP",
"operation": "ValidateClient",
"timestamp": "2019-10-23T08:48:08.474Z",
"msgType": "REQUEST"
},
"Body": {
"validateClientRequest": {
"movil": "04141734272"
}
}
}
}
Which when executing it gives me an answer of not found the mobile, it is a default response when it cannot read the mobile parameter or it is sent empty
My Code
Main Class
package com.app.validate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class ValidateClientApp {
public static void main(String[] args) {
SpringApplication.run(ValidateClientApp.class, args);
}
}
Controller
package com.app.validate.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.app.validate.dao.ValidateClientAppRepository;
import com.app.validate.entity.DriverBonificados;
import com.app.validate.entity.ResponseVo;
#RestController
public class ValidateClientAppController {
#Autowired
private ValidateClientAppRepository dao;
#PostMapping(value = "/ValidateClientApp",consumes = "application/json",produces="application/json")
public ResponseVo ValidateClient(#RequestBody DriverBonificados driver) {
//System.out.println(driver.getMovil());
return dao.validarClienteBonifiado(driver.getMovil());
}
}
Dao
package com.app.validate.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.app.validate.entity.DriverBonificados;
import com.app.validate.entity.ResponseVo;
#Repository
public interface ValidateClientAppRepository extends JpaRepository<DriverBonificados, Integer> {
#Query(nativeQuery = true,value = "call ValidacionClienteBonificado(:movil)")
ResponseVo validarClienteBonifiado(#Param("movil") String pMovil);
}
Entity
package com.app.validate.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="DriverBonificados")
public class DriverBonificados {
#Id
private int id;
private String movil;
private String contador;
private Date fecha_driver;
private Date fecha_alta;
private Date fecha_fin;
private Date codigo_transaccion;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMovil() {
return movil;
}
public void setMovil(String movil) {
this.movil = movil;
}
public String getContador() {
return contador;
}
public void setContador(String contador) {
this.contador = contador;
}
public Date getFecha_driver() {
return fecha_driver;
}
public void setFecha_driver(Date fecha_driver) {
this.fecha_driver = fecha_driver;
}
public Date getFecha_alta() {
return fecha_alta;
}
public void setFecha_alta(Date fecha_alta) {
this.fecha_alta = fecha_alta;
}
public Date getFecha_fin() {
return fecha_fin;
}
public void setFecha_fin(Date fecha_fin) {
this.fecha_fin = fecha_fin;
}
public Date getCodigo_transaccion() {
return codigo_transaccion;
}
public void setCodigo_transaccion(Date codigo_transaccion) {
this.codigo_transaccion = codigo_transaccion;
}
}
Interface Response Stored Procedue
package com.app.validate.entity;
public interface ResponseVo {
String getCode();
String getResult();
}
How could you do to read the Json with header and body? I'm new to spring boot
UPDATE
According to what Silverfang said, I created the classes said by him, but I get an error that I describe next:
BodyRequest.java
public class BodyRequest {
private String validateClientRequest;
private String movil;
public String getValidateClientRequest() {
return validateClientRequest;
}
public void setValidateClientRequest(String validateClientRequest) {
this.validateClientRequest = validateClientRequest;
}
public String getMovil() {
return movil;
}
public void setMovil(String movil) {
this.movil = movil;
}
}
HeaderRequest.java
package com.app.validate.controller;
import java.util.Date;
public class HeaderRequest {
private String country;
private String lang;
private String entity;
private String system;
private String subsystem;
private String operation;
private Date timestamp;
private String msgType;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
public String getSubsystem() {
return subsystem;
}
public void setSubsystem(String subsystem) {
this.subsystem = subsystem;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getMsgType() {
return msgType;
}
public void setMsgType(String msgType) {
this.msgType = msgType;
}
}
RequestBodyDemo.java
package com.app.validate.controller;
public class RequestBodyDemo {
private ValidateClientRequest ValidateClient;
public ValidateClientRequest getValidateClient() {
return ValidateClient;
}
public void setValidateClient(ValidateClientRequest validateClient) {
ValidateClient = validateClient;
}
}
ValidateClientRequest
package com.app.validate.controller;
public class ValidateClientRequest {
private BodyRequest Body;
private HeaderRequest Header;
public BodyRequest getBody() {
return Body;
}
public void setBody(BodyRequest body) {
Body = body;
}
public HeaderRequest getHeader() {
return Header;
}
public void setHeader(HeaderRequest header) {
Header = header;
}
}
My Controller (Update)
package com.app.validate.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.app.validate.dao.ValidateClientAppRepository;
import com.app.validate.entity.DriverBonificados;
import com.app.validate.entity.ResponseVo;
#RestController
public class ValidateClientAppController {
#Autowired
private ValidateClientAppRepository dao;
#PostMapping(value = "/ValidateClientApp",consumes = "application/json",produces="application/json")
public ResponseVo ValidateClient(#RequestBody RequestBodyDemo req) {
System.out.println(req.getValidateClient().getBody().getMovil());
return dao.validarClienteBonifiado(req.getValidateClient().getBody().getMovil());
}
}
The error I get:
From what I understand you have changed the request format and now want the same request body to work for the same controller.
I think you were trying to add the fields to the header. What you are doing here is not the right way to do it. It should goes to header section rather than in the body section of the Postman app. But doing so, you will have to specify these header separately as these are custom headers which will be a lot of work.
Answer to your question
Going by what you were trying to do. Since now you have changed the request body. You will have to make changes in the controller class too. Now it will require three classes If you want to do it in a modular way.
The first class will be BodyRequest.java
private string validateClientRequest;
private string movil;
The next class will be HeaderRequest.java
private string country;
private string lang;
private string entity;
private string system;
private string subsystem;
private string operation;
private Date timestamp;
private string msgType;
Next class will be ValidateClientRequest.java
private HeaderRequest Header;
private BodyRequest Body;
Now for the RequestBodyDemo class;
private ValidateClientRequest ValidateClient;
Note : Use appropriate Getter and setter along with #JsonProperty if you are masking the input request data.
Once these things are done. In your controller Instead of using Entity in #RequestBody Use the class RequestBodyDemo. Once that is done. Just try printing the values just to check whether you are getting them right or not. Then use getter for fetching any value that you need.
Edit :
public ResponseVo ValidateClient(#RequestBody RequestBodyDemo req) {
System.out.println(req.getValidateClient().getBodyrequest().getMovil());
return dao.validarClienteBonifiado(req.getValidateClient().getBodyrequest().getMovil());
}
Note : Use appropriate getter method here.

How to count row table in JPA Query

I'm new to Spring Boot. I have a mysql database, I use a query to count row in my table. But it's not work, it still return my original table data. Can you help me check my code.
Here is my Entity:
#Entity
#Table(name = "act_id_membership", schema = "activiti", catalog = "")
#IdClass(ActIdMembershipEntityPK.class)
public class ActIdMembershipEntity {
private String userId;
private String groupId;
#Id
#Column(name = "USER_ID_")
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
#Id
#Column(name = "GROUP_ID_")
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActIdMembershipEntity that = (ActIdMembershipEntity) o;
return Objects.equals(userId, that.userId) &&
Objects.equals(groupId, that.groupId);
}
#Override
public int hashCode() {
return Objects.hash(userId, groupId);
}
}
Here is my query:
#Repository
public interface MemershipRepository extends JpaRepository<ActIdMembershipEntity, String> {
#Query ("select new com.example.activiti_restful.dtos.UserMembership(i.userId, count(i)) from ActIdMembershipEntity i where i.userId ='kermit'")
UserMembership countMemberships(String userId);
}
Updated code:
My service class:
#Service
public class MembershipService {
#Autowired
private MemershipRepository repository;
public long count() {
return repository.count();
}
My resource class:
#RestController
public class MembershipResource {
#Autowired
private MembershipService membershipService;
#GetMapping("/membership")
public long list() {return membershipService.count();}
}
My custom JSON Object class:
public class UserMembership {
private String userId;
private long numberOfusers;
public UserMembership(String userId, long numberOfusers) {
this.userId = userId;
this.numberOfusers = numberOfusers;
}
}
MySQL Table:
act_id_membership
According repositories documentation using CrudRepository provides a method called count() that is one of the Superinterfaces which JpaRepository is implemented.
Based CrudRepository documentation says:
long count(); Returns the number of entities.
Then you should use CrudRepository method. In addition Remove Uppercase MembershipREPOSITORY, by java code convention, you have to use by following way MembershipRepository.
#Repository
public interface MembershipRepository extends JpaRepository <ActIdMembershipEntity, String> {
}
And use it in your Service:
#Service
public class MembershipService {
#Autowired
private MembershipRepository repo;
public long count() {
return repo.count();
}
}
UPDATED
Based on your requirement:
In Controller:
#RestController
public class MembershipResource {
#Autowired
private MembershipService membershipService;
#GetMapping("/membership")
public List<Object> list() { return membershipService.countMemberships();
}
}
In Service:
#Service
public class MembershipService {
#Autowired
private MemershipRepository repository;
public List<Object> countMemberships() {
return repository.countMemberships();
}
}
In Repository:
#Repository
public interface MemershipRepository extends JpaRepository<ActIdMembershipEntity, String> {
#Query ("select i.userId, count(i) from ActIdMembershipEntity i where i.userId ='kermit'")
List<Object> countMemberships();
}
*> Actually I want it return a json format like [{ name: kermit, value:6}]. Now it just return a number 6 only. How I can do that? Thank you!
First, create a class to wrap your data:
public class UserMembership {
private String userId;
private long numberOfUsers;
public UserMembership(String userId, long numberOfUsers) {
this.userId = userId;
this.numerOfUsers = numberOfUsers;
}
}
Then
#Repository
public interface MembershipRepository extends JpaRepository <ActIdMembershipEntity, String> {
#Query ("select new *****.UserMembership(i.userId, count(i)) from ActIdMembershipEntity i where i.userId = :userId")
UserMembership countMemberships(String userId);
}
*****: your full package name
Hope it help!

How to make #JsonView annotated Rest apis serializes all fields

I have a rest api like "/users/{userId}"
This api returns User data but filters out password by #JsonView(ResourceView.Public.class) annotation.
But I want to get password when the unit test runs.
Is there a way to igore #JsonView annotation when test is running.
Or any other options for me?
public class ResourceView {
public interface Public {}
public interface Friends extends Public {}
public interface Family extends Friends {}
}
public class User {
#JsonView(ResourceView.Public.class)
private String name;
#JsonView(ResourceView.Family.class)
private String password;
}
#RestController
public class UserController {
#Autowired
private UserService userService;
#JsonView(ResourceView.Public.class)
#GetMapping(value = "/users/{userId}")
public User getUser(#PathVariable("userId") String userId) {
return userService.getUser(userId);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles(profiles = "test")
public class UserServiceTest {
#Autowired
protected TestRestTemplate restTemplate;
#Value("${local.server.port}")
private int port;
protected String apiEndpoint;
#Before
protected void setUp() {
initRequestContext();
apiEndpoint = "http://localhost:" + port;
}
protected ResponseEntity<User> requestGetUser(String userId) {
ResponseEntity<User> res = restTemplate.exchange(
apiEndpoint + "/users/" + userId,
HttpMethod.GET,
new HttpEntity<>("parameters", createDefaultHttpHeaders()),
new ParameterizedTypeReference<User>() {});
return res;
}
#Test
public void testGetUser() throws Exception {
ResponseEntity<User> apiRes = requestGetUsers(request);
assertThat(apiRes.getStatusCode(), is(HttpStatus.OK));
User user = apiRes.getBody();
assertThat(user.getName(), is(notNullValue()));
assertThat(user.getPassword(), is(notNullValue()));
}
}
#Configuration
public class MyConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
return objectMapper;
}
}

How do I customize default error message from spring #Valid validation?

DTO:
public class User {
#NotNull
private String name;
#NotNull
private String password;
//..
}
Controller:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(#Valid #RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
Default json error:
{"timestamp":1417379464584,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.User> demo.UserController.saveUser(demo.User), with 2 error(s): [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [may not be null]],"path":"/user"}
I would like to have my custom json for each error occured. How do I accomplish that?
If you want full control over the response message in every controller write a ControllerAdvice. For example, that example transform MethodArgumentNotValidException into a custom json object:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* Kudos http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
*
*/
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MethodArgumentNotValidExceptionHandler {
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public Error methodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private Error processFieldErrors(List<org.springframework.validation.FieldError> fieldErrors) {
Error error = new Error(BAD_REQUEST.value(), "validation error");
for (org.springframework.validation.FieldError fieldError: fieldErrors) {
error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage());
}
return error;
}
static class Error {
private final int status;
private final String message;
private List<FieldError> fieldErrors = new ArrayList<>();
Error(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public void addFieldError(String path, String message) {
FieldError error = new FieldError(path, message);
fieldErrors.add(error);
}
public List<FieldError> getFieldErrors() {
return fieldErrors;
}
}
}
You can perform validation with Errors/BindingResult object.
Add Errors argument to your controller method and customize the error message when errors found.
Below is the sample example, errors.hasErrors() returns true when validation is failed.
#RequestMapping(value = "/user", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> saveUser(#Valid #RequestBody User user, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity(new ApiErrors(errors), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
I know this is kind of old question,
But I just run into it and I found some pretty good article which has also a perfect example in github.
Basically it uses #ControllerAdvice as Spring documentation suggests.
So for example catching 400 error will be achieved by overriding one function:
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.add(error.getField() + ": " + error.getDefaultMessage());
}
for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
}
}
(ApiError class is a simple object to hold status, message, errors)
One way to do it is adding message in #NotNull annotation on entity properties. And adding #Valid annotation in controller request body.
DTO:
public class User {
#NotNull(message = "User name cannot be empty")
private String name;
#NotNull(message = "Password cannot be empty")
private String password;
//..
}
Controller:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(#Valid #RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
// Add one
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<YourErrorResponse>> handleException(MethodArgumentNotValidException ex) {
// Loop through FieldErrors in ex.getBindingResult();
// return *YourErrorReponse* filled using *fieldErrors*
}
#ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandler implements ApplicationContextAware {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public ApplicationError validationException(MethodArgumentNotValidException e) {
e.printStackTrace();
return new ApplicationError(SysMessageEnum.MSG_005, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
}
You can do something like this
#ExceptionHandler(value = MethodArgumentNotValidException.class)
protected ResponseEntity<Error> handleGlobalExceptions(MethodArgumentNotValidException ex,
WebRequest request) {
log.catching(ex);
return new ResponseEntity<>(createErrorResp(HttpStatus.BAD_REQUEST,
ex.getBindingResult().getFieldErrors().stream().map(err -> err.getDefaultMessage())
.collect(java.util.stream.Collectors.joining(", "))),
HttpStatus.BAD_REQUEST);
}
For customized the error message in JSON format then do the below steps.
- Create one #Component called CommonErrorHandler
#Component
public class CommonErrorHandler {
public Map<String,Object> getFieldErrorResponse(BindingResult result){
Map<String, Object> fielderror = new HashMap<>();
List<FieldError>errors= result.getFieldErrors();
for (FieldError error : errors) {
fielderror.put(error.getField(), error.getDefaultMessage());
}return fielderror;
}
public ResponseEntity<Object> fieldErrorResponse(String message,Object fieldError){
Map<String, Object> map = new HashMap<>();
map.put("isSuccess", false);
map.put("data", null);
map.put("status", HttpStatus.BAD_REQUEST);
map.put("message", message);
map.put("timeStamp", DateUtils.getSysDate());
map.put("filedError", fieldError);
return new ResponseEntity<Object>(map,HttpStatus.BAD_REQUEST);
}
}
-- Add InvalidException class
public class InvalidDataException extends RuntimeException {
/**
* #author Ashok Parmar
*/
private static final long serialVersionUID = -4164793146536667139L;
private BindingResult result;
public InvalidDataException(BindingResult result) {
super();
this.setResult(result);
}
public BindingResult getResult() {
return result;
}
public void setResult(BindingResult result) {
this.result = result;
}
}
- Introduce #ControllerAdvice class
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(InvalidDataException.class)
public ResponseEntity<?> invalidDataException(InvalidDataException ex, WebRequest request) {
List<FieldError> errors = ex.getResult().getFieldErrors();
for (FieldError error : errors) {
logger.error("Filed Name ::: " + error.getField() + "Error Message :::" + error.getDefaultMessage());
}
return commonErrorHandler.fieldErrorResponse("Error", commonErrorHandler.getFieldErrorResponse(ex.getResult()));
}
}
-- Use in controller with #Valid and throw exception
public AnyBeans update(**#Valid** #RequestBody AnyBeans anyBeans ,
BindingResult result) {
AnyBeans resultStr = null;
if (result.hasErrors()) {
**throw new InvalidDataException(result);**
} else {
resultStr = anyBeansService.(anyBeans );
return resultStr;
}
}
-- Output will be in JSON format
{
"timeStamp": 1590500231932,
"data": null,
"message": "Error",
"isSuccess": false,
"status": "BAD_REQUEST",
"filedError": {
"name": "Name is mandatory"
}
}
Hope this will be work. :-D
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getBindingResult(): extract the bind result for default message.
String errorResult = ex.getBindingResult().toString();
CustomizedExceptionHandlerResponse exceptionResponse = new CustomizedExceptionHandlerResponse(
errorResult, new Date(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
}
}
class CustomizedExceptionHandlerResponse {
private String message;
private String status;
private Date timestamp;
// constuctor, setters, getters...
}
you can use this code to iterate through errors and build a custom error message :
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.validation.ConstraintViolation;
import java.util.List;
import java.util.stream.Collectors;
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDto> handleException(MethodArgumentNotValidException ex) {
ErrorDto dto = new ErrorDto(HttpStatus.BAD_REQUEST, "Validation error");
dto.setDetailedMessages(ex.getBindingResult().getAllErrors().stream()
.map(err -> err.unwrap(ConstraintViolation.class))
.map(err -> String.format("'%s' %s", err.getPropertyPath(), err.getMessage()))
.collect(Collectors.toList()));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(dto);
}
#Data
public static class ErrorDto {
private final int status;
private final String error;
private final String message;
private List<String> detailedMessages;
public ErrorDto(HttpStatus httpStatus, String message) {
status = httpStatus.value();
error = httpStatus.getReasonPhrase();
this.message = message;
}
}
}
This will give you a response like this in case of error :
{
"status": 400,
"error": "Bad Request",
"message": "Validation error",
"detailedMessages": [
"'yourField' should not be empty."
]
}
Add some information too.
If you use just #Valid, you need to catch BindException. If you use #Valid #RequestBody catch MethodArgumentNotValidException
Some sources:
HandlerMethodArgumentResolverComposite.getArgumentResolver(MethodParameter parameter):129 - search which HandlerMethodArgumentResolver support such parameter
RequestResponseBodyMethodProcessor.supportsParameter(MethodParameter parameter) - return true if parameter has annotation #RequestBody
RequestResponseBodyMethodProcessor:139 - throw MethodArgumentNotValidException
ModelAttributeMethodProcessor:164 - throw BindException
I know this is an old question, but i stumbled across and decided to show a cleaner way of doing what Ksokol showed.
StandardError class:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class StandardError {
private Instant timestamp;
private Integer status;
private String error;
private String message;
private String path;
}
ControllerExceptionHandler class:
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<StandardError> notValid(MethodArgumentNotValidException e, HttpServletRequest request) {
String error = "Dados inválidos.";
HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY;
StandardError err = new StandardError(
Instant.now(),
status.value(),
error,
e.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toSet())
.toString()
.replaceAll("\\[*]*", ""),
request.getRequestURI()
);
return ResponseEntity.status(status).body(err);
}
This way if you have a custom message on your bean validator, it will appear formatted in a error.
Result:

EJB - JPA - Hibernate - JBoss 6-MySql

I am deleveloping web project EJB,JPA - Hibernate as provider, JBoss 6,MySql.
I new in EJB, JPA.I have problems with load ejb bean in servlet.
persistence.xml
<persistence-unit name="LibraryPersistenceUnit" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/MySqlDS</jta-data-source>
<class>library.entity.User</class>
<class>library.entity.Book</class>
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
</properties>
</persistence-unit>
UserFacade.java
#Local
public interface UserFacade {
User findUserByLoginAndPassword(String login, String password);
/* List<User> getClients();
void returnBooks(long userId, long[] ids);
void takeBooks(long userId, long[] ids);*/
}
UserFacadeImpl.java
package library.facades.impl;
import library.dao.impl.UserDAO;
import library.entity.User;
import library.facades.interfaces.UserFacade;
import javax.ejb.EJB;
import javax.ejb.Stateless;
#Stateless
public class UserFacadeImpl implements UserFacade {
#EJB
private UserDAO userDAO;
public UserFacadeImpl() {
}
public User findUserByLoginAndPassword(String login, String password) {
User user = userDAO.selectUserByLoginAndPassword(login, password);
return user;
}
/* public List<User> getListClients() {
List<User> userList = userDAO.getClients();
return userList;
}
public void returnBooks(long userId, long[] ids) {
userDAO.returnBooks(userId, ids);
}
public void takeBooks(long userId, long[] ids) {
userDAO.takeBooks(userId, ids);
}*/
}
UserDAO.java
package library.dao.impl;
import library.dao.interfaces.GenericDAO;
import library.entity.User;
import javax.ejb.Local;
import javax.ejb.Stateless;
import java.util.HashMap;
import java.util.Map;
#Stateless
public class UserDAO extends GenericDAO {
public UserDAO() {
super(User.class);
}
public User selectUserByLoginAndPassword(String login, String password) {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("login", login);
parameters.put("password", password);
return (User) super.findOneResult(User.SELECT_USER_BY_LOGIN_AND_PASSWORD, parameters);
}
/*public List<User> getClients() {
Query namedQuery = entityManager.createNamedQuery(User.ALL_CLIENTS, User.class);
List<User> clientsList = namedQuery.getResultList();
return clientsList;
}
#Override
public void returnBooks(long userId, long[] ids) {
User user = entityManager.find(User.class, userId);
for (long id : ids) {
Book book = entityManager.find(Book.class, id);
user.getTakenBooks().remove(book);
}
entityManager.merge(user);
}
#Override
public void takeBooks(long userId, long[] ids) {
User user = entityManager.find(User.class, userId);
for (long id : ids) {
Book book = entityManager.find(Book.class, id);
user.getTakenBooks().add(book);
}
entityManager.merge(user);
}*/
}
GenericDAO.java
package library.dao.interfaces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.List;
import java.util.Map;
public abstract class GenericDAO<T> {
private static final String LIBRARY_PERSISTENCE_UNIT = "LibraryPersistenceUnit";
#PersistenceContext(unitName = LIBRARY_PERSISTENCE_UNIT)
private EntityManager entityManager;
private Class<T> entityClass;
public GenericDAO() {
}
public GenericDAO(Class<T> entityClass) {
this.entityClass = entityClass;
}
public void save(T entity) {
entityManager.persist(entity);
}
protected void delete(Object id, Class<T> classe) {
T entityToBeRemoved = entityManager.getReference(classe, id);
entityManager.remove(entityToBeRemoved);
}
public T update(T entity) {
return entityManager.merge(entity);
}
public T find(int entityID) {
return entityManager.find(entityClass, entityID);
}
// Using the unchecked because JPA does not have a
// entityManager.getCriteriaBuilder().createQuery()<T> method
#SuppressWarnings({"unchecked", "rawtypes"})
public List<T> findAll() {
/* CriteriaQuery cq = entityManager.getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));*/
return null;//entityManager.createQuery(cq).getResultList();
}
// Using the unchecked because JPA does not have a
// ery.getSingleResult()<T> method
#SuppressWarnings("unchecked")
protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
T result = null;
try {
Query query = entityManager.createNamedQuery(namedQuery);
// Method that will populate parameters if they are passed not null and empty
if (parameters != null && !parameters.isEmpty()) {
populateQueryParameters(query, parameters);
}
result = (T) query.getSingleResult();
} catch (Exception e) {
System.out.println("Error while running query: " + e.getMessage());
e.printStackTrace();
}
return result;
}
private void populateQueryParameters(Query query, Map<String, Object> parameters) {
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
}
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}
LibraryController
package library.controller;
import library.entity.User;
import library.facades.interfaces.UserFacade;
import library.resourses.constants.Constants;
import library.resourses.enums.Role;
import javax.ejb.EJB;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LibraryController extends HttpServlet {
#EJB
private UserFacade userFacade;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException {
performAction(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException {
performAction(request, response);
}
private void performAction(HttpServletRequest request, HttpServletResponse response) {
String pageType = request.getParameter(Constants.PAGE_TYPE);
if (pageType != null) {
try {
String page = null;
String login = request.getParameter(Constants.USER_LOGIN);
String password = request.getParameter(Constants.USER_PASSWORD);
User user = userFacade.findUserByLoginAndPassword(login, password);
if (user == null) {
request.getSession().setAttribute(Constants.ERROR,
Constants.ERROR_MESSAGE_7);
page = Constants.MAIN_PAGE;
} else {
String namePage = user.getRole().toString().toLowerCase();
if (isClient(user)) {
request.getSession().setAttribute(Constants.CLIENT,
user);
request.getSession().setAttribute(Constants.ERROR, null);
} else if (isAdministrator(user)) {
request.getSession().setAttribute(Constants.ADMINISTRATOR,
user);
request.getSession().setAttribute(Constants.ERROR, null);
}
page = Constants.START_PAGES + namePage + Constants.END_PAGES;
}
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher(page);
requestDispatcher.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private boolean isAdministrator(User user) {
return user.getRole().equals(Role.ADMINISTRATOR);
}
private boolean isClient(User user) {
return user.getRole().equals(Role.CLIENT);
}
}
I get null for userFacade.Can you explain me what I do wrong.
Thanks.
The problem is that instead of letting the container instantiate and inject the DAO for you, you explicitely create the DAO instance by yourself:
private IUserDAO dao;
public UserService() {
dao = UserDAOImpl.getInstance();
}
For the EntityManager to be injected into the DAO, the container must instantiate it, not you. Replace the above code with the following, which uses dependency injection, and will thus also have the advantage of making your code testable:
#Inject
private IUserDAO dao;