I have table user and table wallet, table wallet have userId so they are connected.
I created controller like this:
#PostMapping("/user/{user_id}/wallets")
public ResponseEntity<?> createWallet(#PathVariable(value = "user_id") Long user_id,
#RequestBody Wallet walletRequest) {
if (walletRepository.existsByUserIdAndWalletName(user_id, walletRequest.getWalletName())) {
return ResponseEntity.badRequest().body(new MessageResponse("You already have wallet with that name, choose another!"));
}
Wallet comment = userRepository.findById(user_id).map(tutorial -> {
walletRequest.setUser(tutorial);
return walletRepository.save(walletRequest);
}).orElseThrow(() -> new IllegalArgumentException("Not found user with id = " + user_id));
return new ResponseEntity<>(comment, HttpStatus.CREATED);
}
And that works fine when I go in postman and hit API /api/user/1/wallets with the appropriate JSON body, I mean wallet is added to user with ID 1.
But I dont know how to transform this to consume in Thymeleaf?
This is all stuff that is related to this thing:
First of all API to show new wallet form:
#GetMapping("/showNewWalletForm")
public String showNewWalletForm(Model model) {
Wallet wallet = new Wallet();
model.addAttribute("wallet", wallet);
return "new_wallet";
}
Form inside html:
<form action="#" th:action="#{/api/wallet/saveWallet}" th:object="${wallet}" method="POST">
<input type="text" th:field="*{walletName}" placeholder="Wallet name" class="form-control mb-4 col-4">
<input type="text" th:field="*{initialBalance}" placeholder="Enter balance" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2"> Save Wallet</button>
</form>
And API to save wallet:
#PostMapping("/saveWallet")
public String saveWallet(#ModelAttribute("wallet") Wallet wallet) {
// save employee to database
walletService.saveWallet(wallet);
return "redirect:/";
}
Obviously I'm getting Column 'user_id' cannot be null since I didnt set it anywhere as I did in postman.
This is Wallet class:
#Entity
#Table(name = "wallet")
public class Wallet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Please, insert a wallet name")
private String walletName;
private double initialBalance;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
To make this work, you need to be able to fetch the logged-in user and since this is something you will need often, I suggest that you create a Spring managed class.
#Component("CurrentUserUtility")
public class CurrentUserUtility {
public static UserDetailsImpl getCurrentUser() {
Authentication tAuthentication = getAuthentication();
if (tAuthentication != null) {
return (UserDetailsImpl) getAuthentication().getPrincipal();
} else {
return null;
}
}
private static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
UserDetailsImpl is the class implementing the UserDetails interface and this class should have a userId field (or any other field such as username or email that can be used to uniquely identify the current user).
Now, the only thing you need to do in you controller before saving a new wallet is retrieve the current user using the above utility class.
#PostMapping("/saveWallet")
public String saveWallet(#ModelAttribute("wallet") Wallet wallet) {
//user_id shoudn't be null after the next line
wallet.setUser(userRepository.findById( CurrentUserUtility.getCurrentUser().getUserId() ));
walletService.saveWallet(wallet);
return "redirect:/";
}
Related
Using Kotlin.
I'm trying to update a User object by:
send old user to update page;
#GetMapping("/update/{id}")
fun updateUser(#PathVariable(value = "id") id: Int, model: Model): String {
val user = userService.findUserByUid(id)
model.addAttribute("user", user)
if (user.uid != 1)
model.addAttribute("allRoles", userService.addUserRole())
else
model.addAttribute("allRoles", userService.adminRole())
return "updateUser"
}
change some vars of that user in html page;
<form action="#" th:action="#{/userManagement/update}" th:object="${user}" method="POST">
<label>昵称</label>
<input type ="text" th:field = "*{usernickname}" placeholder="usernickname" class="form-control mb-4 col-4">
<br>
<label>密码</label>
<input type ="password" th:field = "*{password}" placeholder="password" class="form-control mb-4 col-4">
<div>
<label>职位:
<input type="radio" name="roles"
th:each="myRole : ${allRoles}"
th:text="${myRole.name()}"
th:value="${myRole}"
th:field="*{usertype}"
th:attr ="checked=${myRole.ordinal()==0?true:false}"
/>
</label>
</div>
<br>
<button type="submit" class="btn btn-update">更新员工</button>
</form>
update user data.
#PostMapping(path = ["/update"])
fun updateEmployee(#ModelAttribute("user") user: User, model: Model): String {
try {
user.password = passwordConfig.passwordEncoder().encode(user.password)
userService.updateUser(user)
} catch (ex: RuntimeException) {
model.addAttribute("error", ex.message)
if (user.uid != 1)
model.addAttribute("allRoles", userService.addUserRole())
else
model.addAttribute("allRoles", userService.adminRole())
return "updateUser"
}
return "redirect:/userManagement/"
}
The problem is, at phase 3, the user object i recieved have only usernickname, password and role, other vars becomes null.
For example, at phase 1, the user object is like{uid=5,username=aaa,usernickname=bbb,password=****,usertype=MANAGER,usergroup=DEFAULT,bugList={}}
and when i changed nickname and password at updateUser page, then push that commit btn, the user object at phase 3 will be{uid=0,username=null,usernickname=ccc,password=*****,usertype=MANAGER,usergroup=null,bugList=null}
it seems like phase 1 didnot run correctly, the user object i want to transmit didnot transmited.
I think it is caused by wrong syntax, but i cannot find how to solve it.
update:
User entity:
#Entity
class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var uid = 0
lateinit var password: String
lateinit var username: String
lateinit var usernickname: String
lateinit var usertype: UserRole
#ManyToOne(fetch= FetchType.EAGER, optional = true)
#JoinColumn(name="groupid")
lateinit var usergroup: UserGroup
#OneToMany(mappedBy = "bid")
lateinit var bugList: List<Bug>
constructor(
password: String,
username: String,
usernickname: String,
usertype: UserRole,
usergroup: UserGroup
) {
this.password = password
this.username = username
this.usernickname = usernickname
this.usertype = usertype
this.usergroup = usergroup
}
constructor()
}
UserRole:
enum class UserRole {
ADMIN, PROGRAMMER, TESTER, MANAGER;
val roleAuthority: GrantedAuthority
get() = SimpleGrantedAuthority("ROLE_$name")
}
I have an existing system that uses string based unique IDs for users and I want to transfer that System into a Spring boot application. I want to creat a user so I send a POST request with the following content:
As you can see, the id gets ignored.
This is my Spring code for the user class:
#PostMapping("/user")
ResponseEntity addUser(User receivedUser) {
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.info("Empfangener User: " + receivedUser.toString());
try {
User mailCheckUser = userService.getUserByMail(receivedUser.getEmail());
User nameCheckUser = userService.getUserByName(receivedUser.getUsername());
if (mailCheckUser != null){
return new ResponseEntity("Email already exists", HttpStatus.NOT_ACCEPTABLE);
}
if (nameCheckUser != null){
return new ResponseEntity("Username already exists", HttpStatus.NOT_ACCEPTABLE);
}
userService.addUser(receivedUser);
} catch (Exception userCreationError) {
return new ResponseEntity(receivedUser, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity(receivedUser, HttpStatus.OK);
}
public void addUser(User user) {
userRepository.save(user);
}
And this is my user class:
#Entity
#Table
public class User {
#Id
#Column(unique =true)
private String id;
private #Column(unique =true)
String username;
private #Column(unique =true)
String email;
private #Column(unique =true)
String simpleAuthToken;
private
String password;
/*REDACTED*/
private
boolean isBlocked;
public User(String id, String name, String email, boolean isBlocked) {
this.id = id;
this.username = name;
this.email = email;
this.simpleAuthToken = simpleAuthToken;
this.isBlocked = false;
}
public User() {
}
/*GETTERS AND SETTERS ARE HERE, BUT I CUT THEM FOR SPACING REASONS*/
}
And this is the Spring Output:
My expected outcome would be that Spring would recognize the id and then create a user with the id I provided. Why is the id always null?
EDIT: If I put the ID in a Put or Get Mapping as Path variable, like so:
#PutMapping("/user/{id}")
ResponseEntity updateUser(#PathVariable String id, User receivedUser) {}
then it gets read and recognized, but it will still be null in the receivedUser
First add #RequestBody in the post request body. In the Post request (/test/user) your passing some params but in the method level not received.
If you want receive id from postman then add #RequestParam("id")String id in the method level.
How you generating unique Id by manually or some generators?
And double check user id at the database console level.
I have a fragment for my web application which is used across multiple pages to display a navbar with the user logged in : if they are logged in, if not : display login & signup button.
I want to know how to include my attribute of a logged in user to a fragment as currently I have the logged in user attribute mapped to each controller displaying a web page, not a fragment.
To get the current logged in user I use
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
However this is only specific to one get request. I want to be able to display the logged in user in a fragment which is then called by each page.
Here is an example of a controller getting the logged in user
#GetMapping(path= "")
public String getMainPage(HttpServletRequest request, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
return "mainpage";
}
Here is my class extending WebSecurityConfigurerAdapter to authenticate a users username and password against one in the database
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
String userbyUsernameQuery = "select username, password, '1' as enabled from auth_user where username=?;";
String rolebyUsernameQuery = "SELECT auth_user.username, auth_role.role_name as authority from auth_user\n" +
"INNER JOIN auth_user_role ON auth_user.auth_user_id = auth_user_role.auth_user_id\n" +
"INNER JOIN auth_role ON auth_role.auth_role_id = auth_user_role.auth_role_id\n" +
"WHERE auth_user.username =?";
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(userbyUsernameQuery)
.authoritiesByUsernameQuery(rolebyUsernameQuery)
.passwordEncoder(passwordEncoder());
}
The part of the navbarHeader fragment I want to change is this
<ul class="nav navbar-nav navbar-right">
<li><a th:href="#{/registration}"><span class="glyphicon glyphicon-user"></span>Sign up</a></li>
<li><a th:href="#{/login}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
</ul>
I want to display the logged in user if logged in & disable the sign up button. If not then display the log in button & signup button
Here is my mainpage page which displays the current user logged in and role which I want to move to the fragment so I do not need to add all the code in each controller & html page.
<div align="right" id="loggedIn" th:if="${loggedinuser != null}">
<div style="margin: 10px"
th:text="'Logged in as: '+${loggedinuser} + ' Role: ' +${roles}">
</div>
</div>
The user class
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User{
private int id;
private String name;
private String lastName;
private String username;
private String password;
private String email;
private String enabled;
public User(String name, Collection<? extends GrantedAuthority> authorities) {
}
}
For this issue I basically utilize ModelAttribute which would be available across all the Presentation compilation. So your controller would like this
public class MyController {
#GetMapping(path= "hi")
public String getMainPage(HttpServletRequest request, Model model) {
return "mainpage";
}
#GetMapping(path= "hello")
public String getMainPage(HttpServletRequest request, Model model) {
return "helloPage";
}
#ModelAttribute("loggedinuser")
public User globalUserObject() {
// Add all null check and authentication check before using. Because this is global
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
// Create User pojo class
User user = new User(authentication.getName(), Arrays.asList(authentication.getAuthorities()))
return user;
}
}
Now the object loggedinuser available for all the pages.
I have to make the switch to selected values within some checkboxes in a jsp page but after the selection and after pressing the "Send" button, I generate this error with the following description: HTTP Status 400: The request sent by the client was syntactically incorrect.
Where am I wrong?
TaskController.java
#RequestMapping(value="/newTask", method = RequestMethod.GET)
public String task(#ModelAttribute Task task, Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String s = auth.getName();
Student student = studentFacade.retrieveUser(s);
List<Job> jobs = new ArrayList<>();
if(!(auth instanceof AnonymousAuthenticationToken)) {
jobs = facadeJob.retriveAlljobs();
model.addAttribute("job", getMathRandomList(jobs));
model.addAttribute("image", imageFacade.retriveAllImages());
List<Image> img = imageFacade.retriveAllImages();
task.setImages(img);
task.setStudent(student);
taskFacade.addTask(task);
List<Long> images = new ArrayList<>();
for(Image i : img)
images.add(i.getId());
model.addAttribute("images", images);
}
return "users/newTask";
}
#RequestMapping(value="/taskRecap", method = RequestMethod.POST)
public String taskRecap(#ModelAttribute Task task, Model model,BindingResult result) {
model.addAttribute("task", task);
return "users/taskRecap";
}
newTask.jsp
<form:form method="post" action="taskRecap" modelAttribute="task" name="form">
<form:checkboxes path="images" items="${images}" value="yes" />
<td><input type="submit" value="Send" /></td>
</form:form>
taskRecap.jsp Immages
<c:forEach var="image" items="${task.images}">
<c:out value="${image.id}" />
</c:forEach>
Task.java
#Entity
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Student student;
#ManyToMany
List<Image> images;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Task() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Task(Long id, Student student, List<Image> images) {
super();
this.id = id;
this.student = student;
this.images = images;
}
public List<Image> getImages() {
return images;
}
public void setImages(List<Image> images) {
this.images = images;
}
}
Using Query parameter
<a href="edit.jsp?Name=${user.name}" />
Using Hidden variable .
<form method="post" action="update.jsp">
<input type="hidden" name="Name" value="${user.id}">
if you use either of the first 2 methods you can access your value like this:
String userid = session.getParameter("Name");
you can send Using Session object.
session.setAttribute("Name", UserName);
These values will now be available from any jsp as long as your session is still active.
if you use the third option you can access your value like this:
String userid = session.getAttribute("Name");
First little background info. Got a fairly standard User Role relationship where the User can have many roles. I have roles defined as a set within the user class. Now I know that html forms have all the values as strings and trying to get values as my custom Role object does not work. I implemented an initbinder to convert the id's back into object so that I can retrieve the selected values off of my checkboxes, that part works.
But I can't seem to go back the other way. I retrieve a User from the database that already has roles and want to pre populate role checkboxes with all the roles that a user has. Based on this example :
Checkboxes example
They say that:
form:checkboxes items="${dynamic-list}" path="property-to-store"
For multiple checkboxes, as long as the “path” or “property” value is
equal to any of the “checkbox values – ${dynamic-list}“, the matched
checkbox will be checked automatically.
My interpretation of that is I should be able to feed it a Set of all the roles and define the path to be the roles from the User object and it should match them thus causing the check box to pre populate.
Every example out there seems to have the value of dynamic-list as a String[]. Well thats great and dandy but how does this work for custom objects that our defined as a Set? Can I still use this one line definition for checkboxes or do I need to do some kind of data binding heading into the view also?
Here is my user dto, user controller, custom form binder, and user edit page.
User DTO
#Entity
#Table
public class User extends BaseDto
{
#Column(updatable = false) #NotBlank
private String username;
#Column(name = "encrypted_password") #Size(min = 6, message = "password must be at least 6 characters") #Pattern(regexp = "^\\S*$", message = "invalid character detected")
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column #NotNull
private boolean enabled;
#Column #Email #NotBlank
private String email;
#Transient
private String confirmPassword;
#ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER, cascade = CascadeType.REFRESH) #JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
public User()
{
}
public User(final String usernameIn, final String passwordIn, final String firstNameIn, final String lastNameIn, final String emailIn, final boolean enabledIn)
{
username = usernameIn;
password = passwordIn;
firstName = firstNameIn;
lastName = lastNameIn;
email = emailIn;
enabled = enabledIn;
}
public String getUsername()
{
return username;
}
public void setUsername(final String usernameIn)
{
username = usernameIn;
}
public String getPassword()
{
return password;
}
public void setPassword(final String passwordIn)
{
password = passwordIn;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(final String firstNameIn)
{
firstName = firstNameIn;
}
public String getLastName()
{
return lastName;
}
public void setLastName(final String lastNameIn)
{
lastName = lastNameIn;
}
public String getEmail()
{
return email;
}
public void setEmail(final String emailIn)
{
email = emailIn;
}
public String getConfirmPassword()
{
return confirmPassword;
}
public void setConfirmPassword(final String confirmPasswordIn)
{
confirmPassword = confirmPasswordIn;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(final boolean enabledIn)
{
enabled = enabledIn;
}
public Set<Role> getRoles()
{
return roles;
}
public void setRoles(final Set<Role> rolesIn)
{
roles = rolesIn;
}
}
User Controller
#Controller #RequestMapping("/user")
public class UserController
{
#Autowired private UserService userService;
#Autowired private UserDao userDao;
#Autowired private RoleDao roleDao;
#InitBinder
public void bindForm(final WebDataBinder binder)
{
binder.registerCustomEditor(Set.class, "roles", new CustomFormBinder<RoleDao>(roleDao, Set.class));
}
#RequestMapping(method = RequestMethod.GET)
public String index(final ModelMap modelMap)
{
return "/user/index";
}
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String create(final ModelMap modelMap)
{
modelMap.addAttribute("userInstance", new User());
modelMap.addAttribute("validRoles", new HashSet<Role>(roleDao.findAll()));
return "/user/create";
}
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(final ModelMap modelMap, #Valid #ModelAttribute("userInstance") final User user, final BindingResult bindingResult)
{
// TODO move to service validation
if (user.getPassword() == null || !user.getPassword().equals(user.getConfirmPassword()) )
{
bindingResult.addError(new FieldError("userInstance", "password", "password fields must match"));
bindingResult.addError(new FieldError("userInstance", "confirmPassword", "password fields must match"));
}
if (user.getRoles() == null || user.getRoles().isEmpty())
{
bindingResult.addError(new FieldError("userInstance", "roles", "Must select at least one role for a User"));
}
if (bindingResult.hasErrors())
{
modelMap.addAttribute("validRoles", new HashSet<Role>(roleDao.findAll()));
return "/user/create";
}
userService.save(user);
return "redirect:/user/list";
}
#RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public String edit(#PathVariable final Integer id, final ModelMap modelMap)
{
final User user = userDao.find(id);
if (user != null)
{
modelMap.addAttribute("userInstance", user);
modelMap.addAttribute("validRoles", new HashSet<Role>(roleDao.findAll()));
return "/user/edit";
}
return "redirect:/user/list";
}
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String editCurrent(final ModelMap modelMap)
{
return edit(userService.getLoggedInUser().getId(), modelMap);
}
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(#Valid #ModelAttribute("userInstance") final User user, final BindingResult bindingResult)
{
if (bindingResult.hasErrors())
{
return "/user/edit";
}
userService.save(user);
return "redirect:/user/list";
}
#ModelAttribute("userInstances")
#RequestMapping(value = "/list", method = RequestMethod.GET)
public List<User> list()
{
return userDao.findAll();
}
}
Custom Form Binder
public class CustomFormBinder<T extends GenericDao> extends CustomCollectionEditor
{
private final T dao;
private static final Logger LOG = LoggerFactory.getLogger(CustomFormBinder.class);
public CustomFormBinder(final T daoIn, final Class collectionType)
{
super(collectionType, true);
dao = daoIn;
}
#Override
protected Object convertElement(final Object element)
{
try
{
// forms should return the id as the itemValue
return dao.find(Integer.valueOf(element.toString()));
}
catch (NumberFormatException e)
{
LOG.warn("Unable to convert " + element + " to an integer");
return null;
}
}
}
User Edit View
<html>
<head>
<title>Create User</title>
</head>
<body>
<c:url value="/user/update" var="actionUrl"/>
<form:form method="post" commandName="userInstance" action="${actionUrl}">
<h1>Edit User ${userInstance.username}</h1>
<div>
<form:label path="username">Username:</form:label>
<form:input path="username" id="username" readonly="true"/>
</div>
<div>
<form:label path="password">Password:</form:label>
<form:input path="password" id="password" type="password" readonly="true"/>
<tag:errorlist path="userInstance.password" cssClass="formError"/>
</div>
<div>
<form:label path="firstName">First Name:</form:label>
<form:input path="firstName" id="firstName"/>
<tag:errorlist path="userInstance.firstName" cssClass="formError"/>
</div>
<div>
<form:label path="lastName">Last Name:</form:label>
<form:input path="lastName" id="lastName"/>
<tag:errorlist path="userInstance.lastName" cssClass="formError"/>
</div>
<div>
<form:label path="email">Email:</form:label>
<form:input path="email" id="email" size="30"/>
<tag:errorlist path="userInstance.email" cssClass="formError"/>
</div>
<div>
**<%--Want to Pre Populate these checkboxed--%>
<form:checkboxes title="Assigned Roles:" path="roles" id="roles" items="${validRoles}" itemLabel="displayName" itemValue="id" element="div"/>**
<tag:errorlist path="userInstance.roles" cssClass="formError"/>
</div>
<form:hidden path="enabled"/>
<form:hidden path="id"/>
<form:hidden path="version"/>
<div class="submit">
<input type="submit" value="Update"/>
Cancel
</div>
</form:form>
</body>
</html>
You need a correct implemented equals method for Role!
If this is not enough have a look at class oorg.springframework.web.servlet.tags.form.AbstractCheckedElementTag. The method void renderFromValue(Object item, Object value, TagWriter tagWriter) is where the the checked flag is set.