I have a Spring Boot Rest API. I want to create users and set a unique constraint on their email and username. That works well so far. Here are the main classes and methods:
#Entity
#NoArgsConstructor
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true)
#NotNull
private String email;
#Column(unique = true)
#NotNull
private String username;
#NotNull
private String password;
public User(String email, String username, String password) {
this.email = email;
this.password = password;
this.username = username;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class SignupRequest {
#NotNull
private String email;
#NotNull
private String username;
#NotNull
private String password;
}
#CrossOrigin(value = "*")
#PostMapping("/signup")
public ResponseEntity<?> signup(#Valid #RequestBody SignupRequest signupRequest) {
signupService.signup(signupRequest);
return ResponseEntity.ok().build();
}
#Service
public class SignupServiceImpl implements SignupService {
#Override
public void signup(SignupRequest signupRequest) throws MessagingException, UnsupportedEncodingException {
User user = new User();
User user = new User(signupRequest.getEmail(), signupRequest.getUsername(), signupRequest.getPassword());
user = userRepository.save(user);
}
}
#Repository
#Component
public interface UserRepository extends CrudRepository<User, Long> {}
Now, the thing is, when I send a POST request to that endpoint with a username or email that already exists, I just get the http response 500 Internal Server Error. But I want to return a different status code and some Error message indicating that the email/username already exists.
Now two questions:
How can I modify the response globally? I could surround the userRepository.save(user) method with a try catch block, but I would have to do that in all the methods where I save a user separately. Can I define something like that globally?
The userRepository.save(user) method just returns a. JdbcSQLIntegrityConstraintViolationException with a pretty verbose message. Is there a way to clearly determine WHAT exactly went wrong (unique username constraint failed, unique email constraint failed, ...)? I could check if a user with that username or email exists by writing a method in the userRepository, but that looks like a lot of unnecessary sql queries to me. Is there a better way?
To answer your first question, You can handle exception globally via spring exception handling mechanism. You could use spring ControllerAdvice. Here you can set generic error response and custom http code. Here is an example of ControllerAdvice
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
String details = ex.getLocalizedMessage();
ErrorResponse error = new ErrorResponse(ApplicationConstants.RECORD_NOT_FOUND, details);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex, WebRequest request) {
String details = ex.getLocalizedMessage();
ErrorResponse error = new ErrorResponse(ApplicationConstants.SERVER_ERROR, details);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
public class ErrorResponse
{
public ErrorResponse(String message, String details) {
super();
this.message = message;
this.details = details;
}
private String message;
private String details;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
Now about second question you can loop through all the cause and check unique constraint name to find out what exception violated. But better approach would be to check first and if found then throw error.
Related
I made a SpringBoot application and now I'm at the login part.
I've made some custom restrictions with a custom annotation.
The problem is that this custom annotation is applied to the user after the password has been ecncripted.
This is the PasswordConstraint
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, User>{
#Override
public void initialize(final ValidPassword arg0){}
#SneakyThrows
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
Properties props = new Properties();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("passay.properties");
props.load(inputStream);
MessageResolver resolver = new PropertiesMessageResolver(props);
PasswordValidator validator = new PasswordValidator(resolver, Arrays.asList(
new LengthRule(8, 16),
new CharacterRule(EnglishCharacterData.UpperCase, 1),
new CharacterRule(EnglishCharacterData.LowerCase, 1),
new CharacterRule(EnglishCharacterData.Digit, 1),
new CharacterRule(EnglishCharacterData.Special, 1),
new WhitespaceRule(),
new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false)
));
RuleResult result = validator.validate(new PasswordData(user.getPasswordHash()));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String messageTemplate = String.join(",", messages);
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
}
This is where I encrypt the password
#Qualifier("getPasswordEncoder")
#Autowired
private PasswordEncoder passwordEncoder;
public void registerNewUserAccount(User user){
Optional<User> userOptional= userRepository.findUserByEmailAddress(user.getEmailAddress());
if(userOptional.isPresent()){
throw new IllegalStateException("email taken!");
}
// Se comenteaza pentru ca: Validarea parolei se face pe hashPassword
//Dupa rezolvarea problemei, se decomenteaza
user.setPasswordHash(passwordEncoder.encode(user.getPasswordHash()));
user.setEnabled(false);
Optional<User> saved = Optional.of(user);
saved.ifPresent(u -> {
try {
String token = UUID.randomUUID().toString();
verificationTokenService.save(user, token);
try {
emailService.sendHtmlMail(u);
} catch (MessagingException e) {
e.printStackTrace();
}
} catch (Exception e){
e.printStackTrace();
}
});
userRepository.save(user);
System.out.println(user);
saved.get();
}
Here is the user:
#Entity
#Table
#ValidPassword
public class User implements UserDetails {
#Id
#SequenceGenerator( //se auto-incrementeaza pkul ?
name = "user_sequence",
sequenceName = "user_sequence",
allocationSize = 1
)
#GeneratedValue( ///??????????
strategy = GenerationType.SEQUENCE,//maybe auto if not working
generator = "user_sequence"
)
//TODO Change camelCase to python_format
private Long id;
private String userName;
#NonNull
#NotBlank(message = "New password is mandatory")
private String passwordHash;
private String firstName;
private String lastName;
private String phoneNumber;
#Email
private String emailAddress;
public String address1;
public String address2;
private String city;
private String country;
private String zipcode;
private boolean enabled;
The issue here is that when you save an entity to the database, the validation annotations are also checked, which will then trigger the ConstraintViolationException.
So the best approach is to follow the Data Transfer Object pattern. I suggest you create a new class say UserDto which will be similar or even a replica of your User entity, ideally your UserDto class should only contain the field that the controller needs inclusive of all the necessary validation annotations.
In the service class where you encrypted your password, before saving the user, convert the UserDto instance to a User entity but remember to remove the #ValidPassword annotation from the User entity since you don't want to validate the encrypted password and you've already checked for the validations from your UserDto class
Remember: UserDto should not be an entity class since we don't want it in our database, we only need it for validation purpose.
Spring boot application I can not get data from oracle database it returns []. In postman, it returns other requests e.g home method in controller class returns correctly. also, the table created by model class the problem is getting data from the table.
Here is the postman result:
I get this in console:
Model class
#Entity // This tells Hibernate to make a table out of this class
public class Userr {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
//Controller Class
#RestController
public class MainController {
#Autowired // This means to get the bean called userRepository
// Which is auto-generated by Spring, we will use it to handle the data
private UserRepository userRepository;
#PostMapping(path="/add") // Map ONLY POST Requests
public #ResponseBody String addNewUser (#RequestParam String name
, #RequestParam String email) {
// #ResponseBody means the returned String is the response, not a view name
// #RequestParam means it is a parameter from the GET or POST request
Userr n = new Userr();
n.setName(name);
n.setEmail(email);
userRepository.save(n);
return "Saved";
}
#GetMapping(path="/all")
public #ResponseBody Iterable<Userr> getAllUsers() {
// This returns a JSON or XML with the users
//
return userRepository.findAll();
}
#GetMapping(path="/al")
public List<Userr> printPersonInfo() {
List<Userr> list = new ArrayList<>();
userRepository.findAll().forEach(list::add);
return list;
}
#RequestMapping("/user")
public String home(){
return "PPPPPP";
}
}
//Repository Class
public interface UserRepository extends CrudRepository<Userr, Integer> {
}
Add #Repository annotation to your UserRepository. It will help with your issue.
I have created the User and Order entities as bellow. What I want to achieve is that if http://localhost:8080/users/username? is given I want to return only the user detail based on username provided. if http://localhost:8080/users/username?detail=true, I want to return user detail and order details for the username provided. How can I achieve this?
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String gender;
private String lastLoggedIn;
#OneToMany
List<Order> listOfOrder;
//constructors
//getter and setter
}
Order.java
#Entity
public class Order
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private float amount;
private String createdAt;
private String deliveredDate;
//constructors
//getter and setter
}
Controller.java
//CREATE CUSTOMER
#RequestMapping(method = POST, value = "/create")
public ResponseEntity createCustomerDetails(#RequestParam String userName, String password, String firstName,
String lastName, String gender) {
String lastLogged = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = new User(userName, password, firstName, lastName, gender, lastLogged);
userRepository.save(user);
return ResponseEntity.status(OK).body(user.getId() + " User were successfully saved");
}
//CREATE ORDER
#RequestMapping(method = POST, value = "/order/{userName}")
public ResponseEntity createOrder(#PathVariable ("userName") String userName, #RequestParam float amount)
{
String createdAt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
String deliveredDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = orderService.findUser(userName);
if (!user.equals(null))
{
Order order = new Order(amount,createdAt,deliveredDate);
user.getListOfOrder().add(order);
return ResponseEntity.status(OK).body("order details were saved under "+user.getUserName() + " "+user.getFirstName());
}
return ResponseEntity.status(NOT_FOUND).body(null + " was not found");
}
//GET THE USER DETAILS
#RequestMapping(method = GET, value = "/users/{userName}")
public ResponseEntity getUserDetail(#PathVariable("userName") String userName,
#RequestParam(defaultValue ="none", required = false) String detail) {
if (!detail.equals("none")){
return .....;
}else {
return ........;
}
}
UserRepository
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
If you're ok with doing the serialization manually, you can employ JsonView to determine what gets serialized.
https://www.baeldung.com/jackson-json-view-annotation
User.java
import com.fasterxml.jackson.annotation.JsonView;
public class User {
#JsonView(Views.Lite.class)
private String name;
#JsonView(Views.Full.class)
private List<Order> orders;
}
Views.java
public class Views {
public static class Lite {}
public static class Full extends Lite {}
}
UserController.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private ObjectMapper mapper;
#GetMapping("/user/{username}")
public ResponseEntity<String> getUserDetail(#PathVariable String username, #RequestParam(required = false) String detail) throws JsonProcessingException {
User user = userRepository.findByUserName(username);
Class viewClass = Views.Lite.class;
if (!StringUtils.isEmpty(detail)) {
viewClass = Views.Full.class;
}
return ResponseEntity.status(HttpStatus.OK)
.body(mapper.writerWithView(viewClass).writeValueAsString(user));
}
}
I am a newbee in java and spring. My first exercise is project to simulate web phone book. I'm stuck with assigning contact to specific user, and later on displaying contacts for that specific user. Any idea or guideline is appreciated.
User class
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String lastName;
private String email;
private String username;
private String password;
#Transient
private String retypePassword;
#ManyToMany(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name="users_roles",
joinColumns = {#JoinColumn(name="user_id", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="role_id", referencedColumnName="roles_id")}
)
private List<Rolee> authorities;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "userForPhonebook")
#Fetch(value = FetchMode.SUBSELECT)
private Collection<Contact> allContacts;
// getters and setters
Contact class
#Entity
public class Contact implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int contactID;
private String name;
private String lastName;
private String email;
private String areaCode;
private String telNumber;
private String adress;
private String city;
private String note;
private String contactImage;
#JoinColumn(name = "users_id")
#ManyToOne(fetch = FetchType.EAGER, optional = false)
private User userForPhonebook;
// getters and setters
ContactServiceImpl class
#Service
public class ContactServiceImpl implements ContactService {
#Autowired
private ContactDAO contactDAO;
#Autowired
private UserDAO userDAO;
#Autowired
private ServletContext context;
#Override
public void addContact(ContactModel contactModel, MultipartFile[] contactImages, User user) {
Contact contact = new Contact();
/*
creating contact
*/
User user2 = userDAO.userGetById(user.getId()); //<-- i cant get user id
contact.setUserForPhonebook(user2);
// --------------------------------
User u = new User(); //
u.setId(2); // hard coding users id...
contact.setUserForPhonebook(u); // and its working fine
// rest of code to create contact ...
UserDAOImpl
// ...
#Override
public void addUser(User user) {
Session s = getCurrentSession();
Transaction trans = s.beginTransaction();
getCurrentSession().save(user);
trans.commit();
}
// ...
UserServiceImpl
// ...
#Override
public void addUser(UserModel userModel) {
User user = new User();
// creating user ...
getUserDAO().addUser(user);
}
// ...
RegisterController
// ...
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public String postRegister(#Valid #ModelAttribute("newUser") UserModel userModel, BindingResult results, ModelMap model) {
if (results.hasErrors()) {
return "addUser";
}
// checking if username, email exist in database ...
// password matching
getUserService().addUser(user);
return "index";
}
// ...
ContactController
// ...
#RequestMapping(value = "/addContact", method = RequestMethod.POST)
public String postAddContact(#Valid #ModelAttribute("addContact") ContactModel contactModel, BindingResult results, HttpServletRequest request, #RequestParam("contactImages") MultipartFile[] contactImages, User user)
throws FileNotFoundException, IOException {
if (results.hasErrors()) {
return "addContact";
}
getContactService().addContact(contactModel, contactImages, user);
return "redirect:phoneBook";
}
//...
You are not specifying how Spring is supposed to bind your User object in your postAddContact signature.
#RequestMapping(value = "/addContact", method = RequestMethod.POST)
public String postAddContact(
// Ok, this is a model retrieved from request params
#Valid #ModelAttribute("addContact") ContactModel contactModel,
// Ok, a BindingResult is mapped when the validation above occurs
BindingResult results,
// Ok, bind the internal HttlServletRequest
HttpServletRequest request,
// Ok, bind this to the multipart part of the request
#RequestParam("contactImages") MultipartFile[] contactImages,
// ... no idea how to bind this
User user)
throws FileNotFoundException, IOException {
// ...
}
You need to specify yourself which user is going to get the contact.
You could add the field inside your ContactModel object, like userId, and in your controller retrieve that user from database before adding the contact.
ContactController.java
// ...
#RequestMapping(value = "/addContact", method = RequestMethod.POST)
public String postAddContact(#Valid #ModelAttribute("addContact") ContactModel contactModel, BindingResult results, HttpServletRequest request, #RequestParam("contactImages") MultipartFile[] contactImages)
throws FileNotFoundException, IOException {
if (results.hasErrors()) {
return "addContact";
}
// Retrieve the user
User user = getUserService().retrieveUser(contactModel.getUserId());
getContactService().addContact(contactModel, contactImages, user);
return "redirect:phoneBook";
}
//...
You could also add a path variable, use the connected user, etc. Above code is just a suggestion.
The answer here helped me to assign currently active user to contact and my postAddContact looks like this. Sorry if I didn't correctly ask the question and I hope this will help someone else
#RequestMapping(value = "/addContact", method = RequestMethod.POST)
public String postAddContact(#Valid #ModelAttribute("addContact") ContactModel contactModel, BindingResult results, HttpServletRequest request, #RequestParam("contactImages") MultipartFile[] contactImages)
throws FileNotFoundException, IOException {
if (results.hasErrors()) {
return "addContact";
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetail = (UserDetails) auth.getPrincipal();
User u = userDAO.getUserByUsername(userDetail.getUsername());
request.getSession().setAttribute("id", u.getId());
int userId = (int) request.getSession().getAttribute("id");
User user = new User();
user.setId(userId);
getContactService().addContact(contactModel, contactImages, user);
return "redirect:phoneBook";
}
I am trying to create a user model with a CrudRepository:
#Entity
public class User {
#Id
#GeneratedValue
private String username;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
public interface UserRepository extends CrudRepository<User, String> {
}
However I got an 500 error every time I call findOne():
#Controller
public class UserController {
#Autowired
private UserRepository users;
#Override
#RequestMapping(value="/register", method=RequestMethod.POST)
public #ResponseBody User register(#RequestBody User userToRegister) {
String username = userToRegister.getUsername();
User user = users.findOne(id);
if (user != null) {
return null;
}
User registeredUser = users.save(userToRegister);
return registeredUser;
}
}
However if I just switch to an long type id instead of username itself then everything works. I think it's common to use string as id. So how to make this work?
I use the embedded hsql database. I didn't wrote any sql code.
The problem is that String username; is annotated with both #Id and #GeneratedValue. #Id means that is should be a primary key, fine it can be a String. But #GeneratedValue means that you want the system to automatically generate a new key when you create a new record. That's easy when the primary key is integer, all databases have a notion of sequence (even if the syntax is not always the same). But if you want String automatically generated keys, you will have do define your own custom generator.
Or if you have no reason for the #GeneratedValue annotation, simply remove it as suggested by Bohuslav Burghardt
Use column annotation like below by putting nullable False.
#Id
#GeneratedValue
#Column(name = "username", nullable = false)
private String username;