Can i send a error-map along with a response object in response? What is the proper way? - spring

I have a spring rest end point which accepts a user object and returns another userobject as response. My controller method looks like:
#PostMapping
public UserResponse createUser(#RequestBody UserDetailsRequestModel userDetails)
throws Exception {
UserRest userResponse= new UserRest();
ModelMapper modelMapper = new ModelMapper();
UserDto userDto = modelMapper.map(userDetails, UserDto.class);
userDto.setRoles(new HashSet<>(Arrays.asList(Roles.ROLE_USER.name())));
UserDto createdUser = userService.createUser(userDto);
returnValue = modelMapper.map(createdUser, UserResponse.class);
return userResponse;
}
My userResponse class looks like
public class UserRest {
private String userId;
private String firstName;
private String lastName;
private String email;
....getters and setters
And this flow is working fine. But now I need to add validation to createUser method (JSR 303) to check if incoming JSON fields are ok. For this I am trying to add below code in my controller
#PostMapping
public UserResponse createUser(#Valid #RequestBody UserDetailsRequestModel userDetails, BindingResult result){
if(result.hasErrors()){
Map<String, String> errorMap = new HashMap<>();
for(FieldError error: result.getFieldErrors()){
errorMap.put(error.getField(), error.getDefaultMessage());
}
**return new ResponseEntity<Map<String, String>>(errorMap, HttpStatus.BAD_REQUEST);**
}
UserRest userResponse= new UserRest();
ModelMapper modelMapper = new ModelMapper();
UserDto userDto = modelMapper.map(userDetails, UserDto.class);
userDto.setRoles(new HashSet<>(Arrays.asList(Roles.ROLE_USER.name())));
UserDto createdUser = userService.createUser(userDto);
returnValue = modelMapper.map(createdUser, UserResponse.class);
return **userResponse**;
The obvious problem in my code is that I can't convert from ResponseEntity<Map<String,String>> to UserResponse object.
Is there a proper way of doing this ? so that I can send errors(if any) or the UserResponse object if there are no errors within the same controller method?

Return a type of ResponseEntity<?>
#PostMapping
public ResponseEntity<?> createUser(#Valid #RequestBody UserDetailsRequestModel userDetails, BindingResult result){
if(result.hasErrors()){
...
return ResponseEntity.badRequest().body(errorMap);
}
...
return ResponseEntity.ok(userRequest);
}

You can return an HttpEntity<?> generic object from a controller method accepting both response type.
You have to wrap the UserResponse response around a ResponseEntity object

Related

Strategy pattern in Spring boot application for payment gateway and methods

I have a controller where I am being passed a gatewayid for different providers. I have a BasePaymentService interface which is implemented by Payu service and Razorpay service for now.
I want to avoid using if condition for and have the strategy added without changing code and have the container inject correct strategy.
How do I add a strategy pattern here?
Would callback method be also a part of strategy here?
How do I account for different payment methods here? (cards, wallets)
BasePaymentService
public interface BasePaymentService {
PaymentDetail makePayment(PaymentDetail detail);
Payment callbackPayment(PaymentCallback detail);
}
Concrete PaymentService
#Service
#Slf4j
public class PayuPaymentService implements BasePaymentService {
#Autowired
PaymentRepository paymentRepository;
public PaymentDetail makePayment(PaymentDetail paymentDetail) {
PaymentUtil paymentUtil = new PaymentUtil();
paymentDetail = paymentUtil.populatePaymentDetail(paymentDetail);
savePaymentDetail(paymentDetail);
return paymentDetail;
}
#Override
public Payment callbackPayment(PaymentCallback paymentResponse) {
log.info("inside callback service >>>>>");
String msg = "Transaction failed.";
Payment payment = paymentRepository.findByTxnId(paymentResponse.getTxnid());
if(payment != null) {
log.info("in condition callback service");
//TODO validate the hash
PaymentStatus paymentStatus = null;
if(paymentResponse.getStatus().equals("failure")){
paymentStatus = PaymentStatus.Failed;
}else if(paymentResponse.getStatus().equals("success")) {
paymentStatus = PaymentStatus.Success;
msg = "Transaction success";
}
payment.setPaymentStatus(paymentStatus);
payment.setMihpayId(paymentResponse.getMihpayid());
payment.setMode(paymentResponse.getMode());
paymentRepository.save(payment);
}
return payment;
}
private void savePaymentDetail(PaymentDetail paymentDetail) {
log.info("in proceedPayment save");
Payment payment = new Payment();
payment.setAmount(Double.parseDouble(paymentDetail.getAmount()));
payment.setEmail(paymentDetail.getEmail());
payment.setName(paymentDetail.getName());
payment.setPaymentDate(new Date());
payment.setPaymentStatus(PaymentStatus.Pending);
payment.setPhone(paymentDetail.getPhone());
payment.setProductInfo(paymentDetail.getProductInfo());
payment.setTxnId(paymentDetail.getTxnId());
paymentRepository.save(payment);
}
}
Controller
#Api(value = "swipe: payment Service", tags = "Example")
#Validated
#RestController
#Slf4j
#RequestMapping(value = CommonConstants.BASE_CONTEXT_PATH)
public class CommonController {
#Autowired
private BasePaymentService paymentService;
#CrossOrigin(origins = "*")
#PostMapping(path = "/payment-details")
public #ResponseBody
PaymentDetail proceedPayment(#RequestBody PaymentDetail paymentDetail){
if(paymentDetail.getGatewayId().equalsIgnoreCase("payu") ){
paymentService.makePayment(paymentDetail);
}
else if(paymentDetail.getGatewayId().equalsIgnoreCase("rp")){
paymentService.makePayment(paymentDetail);
}
return paymentDetail;
}
#CrossOrigin(origins = "*" )
#RequestMapping(path = "/payment-response", method = RequestMethod.POST)
public #ResponseBody
Payment payuCallback(#RequestParam String mihpayid, #RequestParam String status, #RequestParam PaymentMode mode, #RequestParam String txnid, #RequestParam String hash, #RequestParam String amount, #RequestParam String productinfo, #RequestParam String firstname, #RequestParam String lastname, #RequestParam String email, #RequestParam String phone, #RequestParam String error, #RequestParam String bankcode, #RequestParam String PG_TYPE, #RequestParam String bank_ref_num, #RequestParam String unmappedstatus){
log.info("inside callback");
PaymentCallback paymentCallback = new PaymentCallback();
paymentCallback.setMihpayid(mihpayid);
paymentCallback.setTxnid(txnid);
paymentCallback.setMode(mode);
paymentCallback.setHash(hash);
paymentCallback.setStatus(status);
return paymentService.callbackPayment(paymentCallback);
}
}

Spring boot: Sending a JSON to a post request that uses a model as a param

Lets say I have a predefined post mapping as such:
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> addVal(#RequestBody final valDetail newVal) {
//Do Stuff
}
and the valDetail object as follows:
#Data
#Component
#Entity
#Table(name = "val_portal")
public class valDetail {
#Id
#Column(name = "valcode")
private String valCode;
#Column(name = "valname")
private String valName;
}
How would I go about actually sending JSON values from a separate service to this /add endpoint so that they are properly received as a valDetail object?
Currently I tried this implementation but I keep getting a 415 response code.
JSONObject valDetail = new JSONObject();
valDetail.put("valCode",request.getAppCode().toLowerCase());
valDetail.put("valName", request.getProjectName());
String accessToken = this.jwtUtility.retrieveToken().get("access_token").toString();
HttpHeaders authHeaders = new HttpHeaders();
authHeaders.setBearerAuth(accessToken);
authHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(valDetail.toString(), authHeaders);
ResponseEntity<String> loginResponse = restTemplate.exchange(uri,
HttpMethod.POST,
entity,
String.class);
If you want to pass data as json you don't want to take Model try to use #ResponseBody annotation to transfer data through json.
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<String> addVal(#RequestBody final valDetail newVal) {
//Do Stuff
}

How to get the username (email in my case) in Spring Security [UserDetails/String]

I would like to get the e-mail which is username in my application to set the user which send a message. I decided to use typical method i.e. principal and getUsername():
#PostMapping("/messages/{id}")
#ResponseStatus(HttpStatus.CREATED)
public MessageDTO addOneMessage(#RequestBody MessageRequest messageRequest, #PathVariable ("id") Long id) {
checkIfChannelExists(id);
String content = messageRequest.getContent();
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = ((UserDetails) principal).getUsername();
Employee author = employeeRepository.findByEmail(username).get();
Message message = new Message(content, author, id);
messageRepository.save(message);
return new MessageDTO(message);
}
And MessageRequest.java:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MessageRequest {
#NonNull
private String content;
}
But, in this way I still get:
"message": "java.lang.String cannot be cast to org.springframework.security.core.userdetails.UserDetails"
What is wrong in my implementation? To be more precise, I use Postman to test POST requests:
{
"content": "something"
}
If you only need to retrieve the username you can get it through Authentication ie.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
instead of typecasting to your class springcontext provide the almost all details about the user.
If you want the controller to get the user name to test.
please use this code.
//using authentication
#RequestMapping(value = "/name", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Authentication authentication) {
return authentication.name();
}
//using principal
#RequestMapping(value = "/name", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}

Spring REST Service Controller not being validate by #PathVariable and #Valid

#Controller
#EnableWebMvc
#Validated
public class ChildController extends ParentController<InterfaceController> implements InterfaceController{
#Override
#RequestMapping(value = "/map/{name}", produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
#ResponseStatus( HttpStatus.OK)
#ResponseBody
public List<Friends> getAllFriendsByName(
#Valid
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable("name") String name,
#RequestParam(value="pageSize", required=false) String pageSize,
#RequestParam(value="pageNumber", required=false) String pageNumber,
HttpServletRequest request) throws BasicException {
//Some logic over here;
return results;
}
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
Hi, I am trying to do pretty basic validation for a spring request parameter but it just doesn't seem to call the Exception handler, could someone point me into the right direction
P.S. I keep getting NoHandlerFoundException
Spring doesn't support #PathVariable to be validated using #Valid. However, you can do custom validation in your handler method or if you insist on using #Valid then write a custom editor, convert your path variable value to an object, use JSR 303 bean validation and then use #Valid on that object. That might actually work.
Edit:
Here's a third approach. You can actually trick spring to treat your path variable as a model attribute and then validate it.
1. Write a custom validator for your path variable
2. Construct a #ModelAttribute for your path variable and then use #Validator (yes not #Valid as it doesn't let you specify a validator) on that model attribute.
#Component
public class NameValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
String name = (String) target;
if(!StringUtils.isValidName(name)) {
errors.reject("name.invalid.format");
}
}
}
#RequestMapping(value = "/path/{name}", method = RequestMethod.GET)
public List<Friend> getAllFriendsByName(#ModelAttribute("name") #Validated(NameValidator.class) String name) {
// your code
return friends;
}
#ModelAttribute("name")
private String nameAsModelAttribute(#PathVariable String name) {
return name;
}

Request Body with optional property

I have an endpoint which receives a JSON through POST request.
RequestMapping(value = "/app/login", method = RequestMethod.POST,
headers = { "Content-type=application/json" })
#ResponseBody
public LoginResponse logIn(#RequestBody LoginRequest jsonRequest) {
// code
}
LoginRequest:
public class LoginRequest {
private String user;
private String password;
private String idPush;
private Integer idDevice;
// getters and setters
}
Is there anyway I can specify idDevice as optional?
If I don't send idDevice inside the json, Spring returns a 400 error.
It seems that setting the RequestBody to optional, makes any property optional, not only the full bean.
public LoginResponse logIn(#RequestBody(required=false) LoginRequest jsonRequest) {

Resources