I have a problem using mockito in my spring boot project. What should I do to test the business layer? (I only have the builder test).
public class ProductBuilder {
private Product product;
private Collection<Product> products;
public static ProductBuilder mockProductBuilder() {
ProductBuilder builder = new ProductBuilder();
builder.product = new Product("Beer", "Alcholic", "20,99", "Montez");
return builder;
}
public static ProductBuilder mockCollectionProductBuilder() {
ProductBuilder builder = new ProductBuilder();
builder.products = new ArrayList<Product>();
for (int i = 0; i < 10; i++) {
Product product = new Product("Beer " + i, "Alcholic", "20,99" + i, "Montez");
builder.products.add(product);
}
return builder;
}
// Methods
public Product getProduct() {
return this.product;
}
public Collection<Product> getProducts() {
return this.products;
}
You need to have test for your classes controllers, repositories and services. Your builder is fine, your controller should like something like
#SpringBootTest
public class ProductControllerTest {
#InjectMocks
private ProductController productController;
#Mock
private ProductService productService;
#Mock
private BindingResult bindingResult;
private Model model;
private Product productOptional;
private List<Product> products;
private Product product;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.productOptional = ProductBuilder.mockProductBuilder().getProduct();
this.products = (List<Product>) ProductBuilder.mockCollectionProductBuilder().getProducts();
this.product = ProductBuilder.mockProductBuilder().getProduct();
this.model = new ConcurrentModel();
}
#Test
public void index() {
Mockito.when(this.productService.searchAll()).thenReturn(this.products);
Assert.assertEquals(this.productController.index(this.model), "product/index");
Assert.assertEquals(this.model.containsAttribute("products"), true);
}
#Test
public void create() {
Assert.assertEquals(this.productController.add(product, this.model), "product/addProduct");
}
#Test
public void update() {
Mockito.when(this.productService.searchById((long) 1)).thenReturn(this.productOptional);
Assert.assertEquals(this.productController.edit((long) 0, this.model), "product/editProduct");
Assert.assertEquals(this.model.containsAttribute("product"), false);
}
#Test
public void save() {
Mockito.when(this.productService.inserir(this.product)).thenReturn(this.product);
Mockito.when(this.bindingResult.hasErrors()).thenReturn(true);
Assert.assertEquals(this.productController.save(this.product, this.bindingResult, this.model), "product/addProduct");
}
#Test
public void saveError() {
Mockito.when(this.productService.inserir(this.product)).thenReturn(this.product);
Mockito.when(this.bindingResult.hasErrors()).thenReturn(true);
Assert.assertEquals(this.productController.save(this.product, this.bindingResult, this.model), "product/addProduct");
}
#Test
public void delete() {
Assert.assertEquals(this.productController.delete((long) 1, this.model), "product/index");
Mockito.verify(this.productService, Mockito.times(1)).deletar((long) 1);
}
}
For repositories
#RunWith(SpringRunner.class)
#SpringBootTest
public class ProductRepositoryTest {
#Autowired
private ProductRepository productRepository;
private Product product;
#Before
public void setUp() {
this.product = ProductBuilder.mockProductBuilder().getProduct();
}
#After
public void after() {
this.productRepository.deleteAll();
}
#Test
public void findAll() {
Assert.assertThat(this.productRepository.findAll(), instanceOf(List.class));
}
#Test
public void findById() {
Assert.assertThat(this.productRepository.findById((long) 2), instanceOf(Optional.class));
}
#Test
public void saveCreate() {
this.product.setId((long) 1);
this.product = this.productRepository.save(this.product);
//Assert.assertNotEquals(this.product.getId(), 1);
}
#Test
public void saveUpdate() {
this.product.setId((long) 0);
this.product = this.productRepository.save(this.product);
this.product.setNome("Jonas");
Product productAtualizado = this.productRepository.save(this.product);
Assert.assertEquals(productAtualizado.getNome(), this.product.getNome());
}
#Test
public void deleteById() {
this.product = this.productRepository.save(this.product);
this.productRepository.deleteById(this.product.getId());
Assert.assertEquals(this.productRepository.findAll().size(), 0);
}
For Services
#SpringBootTest
public class ProductServiceTest {
#InjectMocks
private ProductService productService;
#Mock
private ProductRepository productRepository;
private Product productTest;
private List<Product> products;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.productTest = ProductBuilder.mockProductBuilder().getProduct();
this.products = (List<Product>) ProductBuilder.mockCollectionProductBuilder().getProducts();
}
#Test
public void buscarTodosTest() {
Mockito.when(this.productRepository.findAll()).thenReturn(this.products);
List<Product> productsBd = this.productService.buscarTodos();
assertEquals(productsBd, this.products);
}
#Test
public void buscarPeloId() {
Optional<Product> productOptional = Optional.of(this.productTest);
Mockito.when(this.productRepository.findById(this.productTest.getId())).thenReturn(productOptional);
Product product = this.productService.buscarPeloId(this.productTest.getId());
assertEquals(product, productOptional.get());
}
#Test
public void inserirTest() {
Mockito.when(this.productRepository.save(this.productTest)).thenReturn(this.productTest);
Product productSalvo = this.productService.inserir(this.productTest);
assertEquals(productSalvo, this.productTest);
}
#Test
public void atualizarTest() {
Optional<Product> productOptional = Optional.of(this.productTest);
this.productTest.setId((long) 1);
Mockito.when(this.productRepository.save(this.productTest)).thenReturn(this.productTest);
Mockito.when(this.productRepository.findById(this.productTest.getId())).thenReturn(productOptional);
Product productAtualizado = this.productService.atualizar(this.productTest);
assertEquals(productAtualizado, this.productTest);
}
#Test
public void deletarTest() {
Optional<Product> productOptional = Optional.of(this.productTest);
Mockito.when(this.productRepository.findById(this.productTest.getId())).thenReturn(productOptional);
this.productService.deletar(this.productTest.getId());
Mockito.verify(this.productRepository, Mockito.times(1)).findById(this.productTest.getId());
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="MyMusic">
<meta name="author" content="Victoria Brandt">
<title>Users - MyMusic</title>
</head>
<body>
<nav>
<ul>
<li>User</li>
<li>Music</li>
<li>Playlist</li>
</ul>
</nav>
<div>
<div >
<div>
<strong><h2>Users</h2></strong>
</div>
</br>
<div >
<a th:href="#{/users/addUser}" >Add new user</a>
</div>
</br>
<div>
<div >
<table >
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.location}"></td>
<td >
<div >
<a th:href="#{/users/editUser/{id}(id=${user.id})}" >Edit</a>
<a class="delete btn btn-sm btn-danger" th:href="#{/users/deleteUser/{id}(id=${user.id})}">Delete</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="MyMusic">
<meta name="author" content="Victoria Brandt">
<title>Users - MyMusic</title>
</head>
<body>
<nav>
<ul>
<li>User</li>
<li>Music</li>
<li>Playlist</li>
</ul>
</nav>
<div>
<div>
<div>
<strong><h2>Create new user</h2></strong>
</div>
</br>
<div>
<div>
<form th:object="${user}" th:action="#{/users/saveUser}"
method="POST">
<div>
<fieldset>
<div>
<div th:if="${#fields.hasAnyErrors()}">
<div th:each="detailedError : ${#fields.detailedErrors()}">
<span th:text="${detailedError.message}"></span>
</div>
</div>
</div>
<div>
<div>
<input type="text" id="id" th:field="*{id}" readOnly="readonly" />
</div>
</div>
<div>
<div th:classappend="${#fields.hasErrors('name')}? 'has-error'">
<label>Name</label> <input type="text" th:field="*{name}"
autofocus="autofocus" />
</div>
</div>
<div>
<div
th:classappend="${#fields.hasErrors('location')}? 'has-error'">
<label>Location</label> <input type="text"
th:field="*{location}" />
</div>
</div>
</fieldset>
</div>
<div >
<button type="submit" >Save</button>
<a th:href="#{/users}">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
Related
My page doesn’t open. On this page I can create a user and give him a username and password + role (admin or user). Why does page doesn t work. You can look at the code, it seems to have written correctly. Maybe somewhere I made a mistake in the code.Tomcat gives error 404. I just need him to open this page, you can see the controller
Admin Controller
#Controller
#RequestMapping("/admin")
public class AdminController {
#Autowired
private StudentService studentService;
#GetMapping("/allStudentsAdmin")
public ModelAndView allStudentsForUser() {
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudentsAdmin");
return mv;
}
#GetMapping(value = "/deleteStudent/{id}")
public ModelAndView deleteUserById(#PathVariable Long id) {
studentService.deleteStudentById(id);
ModelAndView mv = new ModelAndView("redirect:/admin/allStudentsAdmin");
return mv;
}
#GetMapping(value = "/editStudent/{id}")
public ModelAndView displayEditUserForm(#PathVariable Long id) {
ModelAndView mv = new ModelAndView("adminEditStudent");
Student student = studentService.getStudentById(id);
mv.addObject("headerMessage", "Редактирование студента");
mv.addObject("student", student);
return mv;
}
#PostMapping(value = "/editStudent")
public String saveEditedUser(
#RequestParam("id") Long id,
#RequestParam("name") String name,
#RequestParam("surname") String surname,
#RequestParam("avatar") MultipartFile file) {
try {
studentService.updateStudent(name, surname, file, studentService.getStudentById(id));
} catch (FileSystemException ex) {
ex.printStackTrace();
} catch (IOException e) {
return "redirect:/errors";
}
return "redirect:/admin/allStudentsAdmin";
}
#GetMapping(value = "/addStudentAdmin")
public ModelAndView displayNewUserForm() {
ModelAndView mv = new ModelAndView("addStudentAdmin");
mv.addObject("headerMessage", "Add Student Details");
mv.addObject("student", new Student());
return mv;
}
#PostMapping(value = "/addStudentAdmin")
public String saveNewStudent(#RequestParam("name") #NonNull String name,
#RequestParam("surname") #NonNull String surname,
#RequestParam("avatar") MultipartFile file)
throws IOException {
Student student = new Student();
student.setSurname(surname);
student.setName(name);
if (file != null && !file.isEmpty()) {
student.setAvatar(studentService.saveAvatarImage(file).getName());
}
studentService.saveStudent(student);
return "redirect:/admin/allStudentsAdmin";
}
#GetMapping(value = "/addUser")
public ModelAndView displayAddUserForm() {
ModelAndView mv = new ModelAndView("addStudentAdmin");
mv.addObject("headerMessage", "Add Student Details");
mv.addObject("student", new Student());
return mv;
}
#PostMapping(value = "/addUser")
public String saveNewUser(#RequestParam("name") #NonNull String name,
#RequestParam("surname") #NonNull String surname,
#RequestParam("role") #NonNull String role)
throws IOException {
Student student = new Student();
student.setSurname(surname);
student.setName(name);
studentService.saveStudent(student);
return "redirect:/admin/allStudentsAdmin";
}
}
Admin Decorator
<body>
<div id="container">
<div id="header">
</div>
<div id="nav">
<ul>
<li><span>Главная</span></li>
<li class="dropdown"><span>Студенты</span>
<ul>
<li><span>Список студентов</span></li>
<sec:authorize access="hasRole('ADMIN') || hasRole('USER')">
<li><span>Добавить студента</span></li>
</sec:authorize>
<sec:authorize access="hasRole('ADMIN')">
<li><span>Добавить юзера</span></li>
</sec:authorize>
</ul>
</li>
<li><a><span>О нас </span></a></li>
<sec:authorize access="!isAuthenticated()">
<li><span>Выйти</span></li>
</sec:authorize>
</ul>
</div>
</div>
<sitemesh:write property='body'/>
<jsp:include page="/WEB-INF/template/admintemplate.jsp"/>
</body>
</html>
AddUser.JSP
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<title>Home</title>
</head>
<body>
<div class="add">
<br>
<br>
<br>
<br>
<center>
<h1>${headerMessage}</h1>
<form:form method="POST" action="${pageContext.request.contextPath}/admin/addUser" enctype="multipart/form-data">
<table>
<tr>
<td><label path="Name">Name</label></td>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<td><label path="Surname">Surname</label></td>
<td><input type="text" name="surname"/></td>
</tr>
<tr>
<td><select name="select" size="3" multiple>
<option selected value="s1">Admin</option>
<option value="s2">User</option>
</select></td>
<td>
<input type="text" name="Role"/>
</td>
</tr>
<tr>
<td><input class="btn btn-primary" type="submit" value="Добавить"></td>
</tr>
</table>
</form:form>
</center>
</div>
</body>
</html>
Change URL to admin/addUser
because in your controller you have added a root level admin
#RequestMapping("/admin")
ex .http://localhost:8080/SchoolMaven/admin/addUser
I create a project where admin and user can log in. I have JSP pages, only the admin can enter there, because access is restricted to the user. The user can go to certain pages.
I have two pages "allStudents.jsp" - only admin can go there.
And on this page "allStudentsUser.jsp" - only the user can enter.
So this is how to write the code in the controller correctly so that Tomkat read my "allStudentsUser.jsp" page.
Student Controller
#Controller
public class StudentController {
#Autowired
private ServletContext servletContext;
// Constructor based Dependency Injection
private StudentService studentService;
public StudentController() {
}
#Autowired
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
#RequestMapping(value = "/allStudents", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUser() {
System.out.println("User Page Requested : All Students");
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudents");
return mv;
}
#RequestMapping(value = "/allStudentsUser", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUsers() {
System.out.println("User Page Requested : All Students");
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudentsUser");
return mv;
}
#RequestMapping(value = "/addStudent", method = RequestMethod.GET)
public ModelAndView displayNewUserForm() {
ModelAndView mv = new ModelAndView("addStudent");
mv.addObject("headerMessage", "Add Student Details");
mv.addObject("student", new Student());
return mv;
}
#PostMapping(value = "/addStudent")
public String saveNewStudent(#RequestParam("name") #NonNull String name,
#RequestParam("surname") #NonNull String surname,
#RequestParam("avatar") MultipartFile file)
throws IOException {
Student student = new Student();
student.setSurname(surname);
student.setName(name);
if (file != null && !file.isEmpty()) {
student.setAvatar(studentService.saveAvatarImage(file).getName());
}
studentService.saveStudent(student);
return "redirect:/allStudents";
}
#GetMapping(value = "/editStudent/{id}")
public ModelAndView displayEditUserForm(#PathVariable Long id) {
ModelAndView mv = new ModelAndView("editStudent");
Student student = studentService.getStudentById(id);
mv.addObject("headerMessage", "Редактирование студента");
mv.addObject("student", student);
return mv;
}
#PostMapping(value = "/editStudent")
public String saveEditedUser(
#RequestParam("id") Long id,
#RequestParam("name") String name,
#RequestParam("surname") String surname,
#RequestParam("avatar") MultipartFile file) {
try {
studentService.updateStudent(name, surname, file, studentService.getStudentById(id));
} catch (FileSystemException ex) {
ex.printStackTrace();
} catch (IOException e) {
return "redirect:/error";
}
return "redirect:/allStudents";
}
#GetMapping(value = "/deleteStudent/{id}")
public ModelAndView deleteUserById(#PathVariable Long id) {
studentService.deleteStudentById(id);
ModelAndView mv = new ModelAndView("redirect:/allStudents");
return mv;
}
}
Security Config
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("1234")).roles("ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("user1234")).roles("USER")
.and();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/allStudentsUser**").permitAll()
.antMatchers("/allStudents**").hasRole("ADMIN")
.antMatchers("/addStudent/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/editStudent/**").hasRole("ADMIN")
.antMatchers("/deleteStudent/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/allStudents")
.failureUrl("/login?error=true")
.and()
.logout()
.logoutSuccessUrl("/login?logout=true")
.and()
.csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
AllStudents.jsp (for Admin)
<%# page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%>
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<link href="../css/style.css" rel="stylesheet" type="text/css">
<style><%#include file="/css/style.css"%></style>
<title>Все студенты</title>
</head>
<body>
<br>
<br>
<br>
<br>
<div class="it">
<h3>Список всех студентов</h3>
${message}
<br>
<br>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Surname</th>
<th scope="col">Avatar</th>
</tr>
</thead>
<tbody>
<c:forEach var="student" items="${studentList}">
<tr>
<th scope="row">1</th>
<td>${student.name}</td>
<td>${student.surname}</td>
<td><img src="${pageContext.request.contextPath}/avatar?avatar=${student.avatar}" style="max-height: 200px; max-width: 200px;" /></td>
<td>
<sec:authorize access="hasRole('ADMIN')">
<a href="${pageContext.request.contextPath}/editStudent/${student.id}">
<button type="button" class="btn btn-primary">Edit</button>
</a>
</sec:authorize>
</td>
<td>
<sec:authorize access="hasRole('ADMIN')">
<a href="${pageContext.request.contextPath}/deleteStudent/${student.id}">
<button type="button" class="btn btn-primary">Delete</button>
</a>
</sec:authorize>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</body>
</html>
AllStudentsUser.jsp (For User)
<%# page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%>
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<link href="../css/style.css" rel="stylesheet" type="text/css">
<style><%#include file="/css/style.css"%></style>
<title>Все студенты</title>
</head>
<body>
<br>
<br>
<br>
<br>
<div class="it">
<h3>Список всех студентов</h3>
${message}
<br>
<br>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Surname</th>
<th scope="col">Avatar</th>
</tr>
</thead>
<tbody>
<c:forEach var="student" items="${studentList}">
<tr>
<th scope="row">1</th>
<td>${student.name}</td>
<td>${student.surname}</td>
<td><img src="${pageContext.request.contextPath}/avatar?avatar=${student.avatar}" style="max-height: 200px; max-width: 200px;" /></td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</body>
</html>
Student Controller (add Secured)
#RequestMapping(value = "/allStudents", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUser() {
System.out.println("User Page Requested : All Students");
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudents");
return mv;
}
#Secured("ROLE_ADMIN")
#RequestMapping(value = "/allStudentsAdmin", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUsers() {
System.out.println("User Page Requested : All Students");
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudentsUser");
return mv;
}
#Secured("ROLE_USER")
#RequestMapping(value = "/allStudentsUser", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUsers() {
System.out.println("User Page Requested : All Students");
ModelAndView mv = new ModelAndView();
List<Student> studentList = studentService.getAllStudents();
mv.addObject("studentList", studentList);
mv.setViewName("allStudentsUser");
return mv;
}
i recommend to use #Secured("ADMIN") or #Secured("USER").
if you want use it, add to #EnableGlobalMethodSecurity(securedEnabled = true) at SecurityConfig.java.
example for your code:
#Secured("ROLE_ADMIN")
#RequestMapping(value = "/allStudents", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUser() {
// ...
}
#Secured("ROLE_USER")
#RequestMapping(value = "/allStudentsUser", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView displayAllUsers() {
// ...
}
guide link : https://docs.spring.io/spring-security/site/docs/5.2.0.M3/reference/htmlsingle/#jc-method
I am using spring boot+thymeleaf+neo4j. Everything is working fine except that thymeleaf is not able to resolve a few of the attributes of the 'product' variable used in the th:each block in the template product_grid.html, which includes th:src="${product.URL}", th:text="${Product.title}" and the th:action="#{/product/(${Product.getId()})}" expression in form tag. The th:text="${Product.Price}" is working. When I check the code produced in the browser the src tag is empty (src:""), the text attribute containing the title tag is not shown in the browser. The th:action works fine but when I click the button defined inside the form, the url changes to http://localhost:8080/product/?btn=View+Product
instead of the following code which is shown in the browser console
http://localhost:8080/product/?1
Note: I am trying to get the image url from a field which is stored in neo4j database. The project directory is:
project directory image
Template:product_grid.html
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Products</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Grada</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link">My Best Products</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="#{/login}">Login</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<a class="btn btn-outline-success my-2 my-sm-0" href="file:///home/madhav/SPM/Grada/public_html/product.html">Search</a>
</form>
</div>
</nav>
<div class="container text-center">
<div class="row">
<div th:each="Product:${products}" class="col-lg-3 col-sm-12 col-md-6 my-2 p-auto">
<div class="card">
<div class="card-body">
<img src="http://via.placeholder.com/150x150/888/111" th:src="${Product.URL}" alt="img" class="card-img-top img-thumbnail img-fluid">
<div class="card-title lead" th:text="${Product.title}">Some product name</div>
<div class="card-text">Price: ₹<span th:text="${Product.Price}">400</span></div>
</div>
<form method="GET" action="/" th:action="#{/product/(${Product.getId()})}">
<input type="submit" name="btn" class="form-control btn btn-primary" value="View Product">
<input type="submit" name="btn" class="form-control btn btn-primary" value="Add to Cart">
</form>
</div>
</div>
</div>
</div>
</body>
</html>`
Product model:Product.html
package com.grada.ecommerce.Models;
import com.grada.ecommerce.Models.Seller;
import org.neo4j.ogm.annotation.*;
import java.util.HashSet;
import java.util.Set;
#NodeEntity(label = "Product")
public class Product
{
public Product()
{
}
public Product(String title, Double price, int quantity, float rating, String description, String url, String company)
{
this.title = title;
this.Rating = rating;
this.Description = description;
this.Price = price;
this.Quantity = quantity;
this.URL = url;
this.Company = company;
}
#Id
#GeneratedValue
private Long id;
#Property(name = "title")
public String title;
#Property(name = "Rating")
public float Rating;
#Property(name = "Description")
public String Description;
#Property(name = "Price")
public Double Price;
#Property(name = "Quantity")
public int Quantity;
#Property(name = "Company")
public String Company;
#Property(name = "URL")
public String URL;
#Override
public String toString()
{
return this.title;
}
public Long getId() {
return id;
}
public String getTitle()
{
return title;
}
#Relationship(type = "Sells", direction = Relationship.INCOMING)
public Seller Seller;
}
ProductController.java
package com.grada.ecommerce.Controllers;
import com.grada.ecommerce.Models.Product;
import com.grada.ecommerce.Models.Seller;
import com.grada.ecommerce.Services.ProductService;
import com.grada.ecommerce.Services.SellerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
public class ProductController
{
final ProductService productService;
final SellerService sellerService;
#Autowired
public ProductController(ProductService productService, SellerService sellerService)
{
this.productService = productService;
this.sellerService = sellerService;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String Index(Model model)
{
Iterable<Product> products = productService.products();
model.addAttribute("products", products);
return "product_grid";
}
#RequestMapping(value = "/product", method = RequestMethod.GET)
public String ShowProduct(#RequestParam(value = "id") Long id, Model model)
{
Product product = productService.findProductByID(id);
if(product == null)
return "redirect:/";
model.addAttribute("product", product);
return "productid";
}
#RequestMapping(value = "/add")
public String AddProduct(Model model)
{
model.addAttribute("product", new Product());
return "add";
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String AddProduct(#ModelAttribute Product product)
{
productService.addProduct(product);
return "redirect:/";
}
#RequestMapping(value = "/delete", method = RequestMethod.GET)
public String DeleteProduct(Model model)
{
model.addAttribute("product", new Product());
return "delete";
}
#RequestMapping(value = "/delete", method = RequestMethod.POST)
public String DeleteProduct(#ModelAttribute Product product)
{
productService.deleteProduct(product);
return "redirect:/";
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String LoginPage(Model model)
{
return "login";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String Authenticate(Model model, String username, String password)
{
if (username.equalsIgnoreCase("seller#fake.com") && password.equals("fakeseller"))
{
Iterable<Product> products = productService.products();
model.addAttribute("products", products);
return "loggedin";
}
else
return "redirect:/login";
}
#RequestMapping(value = "/policy", method = RequestMethod.GET)
public String PolicyPage()
{
return "policies";
}
}
Welcome to SO.
Include setX methods for your variables in the Product class. Thymeleaf needs these to bind.
IMO, a great way to do this is to use Project Lombok and simply annotate your class with #Data. Then you won't even need to specify getters or setters (or your toString()) at all.
Use lower-case for your variables since by convention variables with a capital first letter refers to the class, not an instance variable.
As mentioned, use POST instead of GET in your form since you are submitting data.
Use the shorthand #GetMapping and #PostMapping to make it easier to read.
I'm using a simple Spring form with mustache. However the data is not received in Spring controller. login.getId(), login.getPass() are always received as null in controller. Any clues if something have to be fixed in template or controller?
My template and controller code as below.
<form class="form-signin" id="loginForm" action="{{{appCtxt}}}/login" method="post">
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" name="{{id}}" id="id" class="form-control" placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="{{pass}}" id="pass" class="form-control" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
Controller
#Controller
public class LoginController {
private LoginService loginService;
#Autowired
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest request, Model model) {
ModelAndView result = new ModelAndView();
model.addAttribute("login", new Login());
result.addObject("resources", request.getContextPath() + "/resources");
result.addObject("appCtxt", request.getContextPath());
// return "redirect:/users";
result.setViewName("home");
return result;
}
#RequestMapping(value = "login", method = RequestMethod.POST)
public String login(Login login, HttpServletRequest request){
boolean status = loginService.verifyLogin(login.getId(), login.getPass());
if(status == true) {
return "redirect:/users";
}
else
{
return "error";
}
}
}
Instead of the name="{{pass}}" you can use name="pass" assuming you Login class contains a field with the very same name. Another thing is that you need '#ModelAttribute' annotation near the Login parameter.
For easier understanding how it works, please consider following example:
Student.java
package me.disper.model;
public class Student {
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
student.html
<!DOCTYPE html>
<html lang="en" xmlns:form="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Create new student</title>
</head>
<body>
<form name="student" action="add" method="post">
Name: <input type="text" name="name" /><br/>
Surname: <input type="text" name="surname" /><br/>
<input type="submit" value="Save" />
</form>
</body>
</html>
MyMustacheController.java
package me.disper;
import me.disper.model.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class MyMustacheController {
#GetMapping("/student")
public ModelAndView createStudent(){
ModelAndView modelAndView = new ModelAndView("student", "student", new Student());
return modelAndView;
}
#PostMapping("/add")
public ModelAndView addStudent(#ModelAttribute Student student){
ModelAndView modelAndView = new ModelAndView("created", "student", student);
return modelAndView;
}
}
created.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Student created</title>
</head>
<body>
{{#student}}
Hello {{name}} {{surname}}
{{/student}}
</body>
</html>
Take a look at the following tutorial: A guide to forms in Spring MVC. There are some helpful hints like #ModelAttribute.
I have a page that displays the list of all the journals available. I want to write a thymeleaf expression language that highlights the already subscribed journals using there journal id . So for all the subscribed journals the text for the hyper-link href should be "Unsubscribe" and vice verse if its not subscribed.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<title>TBD</title>
<!--/*/ <th:block th:include="fragments/headinc :: head"></th:block> /*/-->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<h1 th:text="'Hello, ' + ${user.fullname} + '!'" />
<p>Manage your subscriptions here</p>
<form role="form" id="form-search" class="form-inline"
th:action="#{/subscriptions}" method="get">
<input type="text" class="form-control" id="filter" name="filter"
placeholder="Enter filter"></input>
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-search"></span> Search
</button>
<a th:href="#{/logout}" class="btn btn-link" role="button">Logout</a>
</form>
<div th:if="${not #lists.isEmpty(journals)}">
<form role="form" id="form-subscribe" th:action="#{/subscribe}"
method="post">
<input type="hidden" name="journalId" id="journalId" />
</form>
<table id="table" class="table">
<thead>
<tr>
<th>Subject</th>
<th>Filename</th>
<th>Tags</th>
<th>View</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="journal : ${journals}">
<td th:text="${journal.subject}">Id</td>
<td th:text="${journal.filename}">Product Id</td>
<td th:text="${journal.tags}">Description</td>
<td><a>View</a></td>
<td><a id="href"
th:href="'javascript:subscribe(\'' + ${journal.id} + '\');'">Subscribe</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
<script type="text/javascript">
function subscribe(journalId) {
$('#journalId').val(journalId);
$('#form-subscribe').submit();
}
</script>
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
$(document).ready(function() {
var modelAttributeValue = [[${subscriptions}]];
console.log(modelAttributeValue);
alert(modelAttributeValue);
var array = modelAttributeValue.split(';');
console.log(array);
alert(array);
});
/*]]>*/
</script>
</html>
Controller
#Controller
public class SubscriptionController {
#Autowired
private SubscriberService subscriberService;
#RequestMapping(value = "/subscribe", method = RequestMethod.POST)
String subscribe(Model model, #RequestParam("journalId") Integer journalId) {
JournalToken token = (JournalToken) SecurityContextHolder.getContext().getAuthentication();
Account user = (Account) token.getCredentials();
model.addAttribute("user", user);
Journal journal = this.subscriberService.findJournalById(journalId);
this.subscriberService.subscribeJournalForSubscriber(journal, user);
return "redirect:subscriptions";
}
#RequestMapping(value = "/subscriptions", method = RequestMethod.GET)
String list(Model model) {
JournalToken token = (JournalToken) SecurityContextHolder.getContext().getAuthentication();
Account user = (Account) token.getCredentials();
model.addAttribute("user", user);
ArrayList<Journal> journals = this.subscriberService.FindAllJournals();
model.addAttribute("journals", journals);
StringBuilder sub = new StringBuilder();
ArrayList<Subscription> subscribed = this.subscriberService.getSubscribedJournalsForSubscriber(user);
model.addAttribute("subscriptions", subscribed);
return "subscriptions";
}
}
Model subscriptions
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"userId", "journalId"})})
public class Subscription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Version
private Integer version;
private Integer userId;
private Integer journalId;
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return this.id;
}
public void setVersion(Integer version) {
this.version = version;
}
public Integer getVersion() {
return this.version;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getUserId() {
return this.userId;
}
public void setJournalId(Integer journalId) {
this.journalId = journalId;
}
public Integer getJournalId() {
return this.journalId;
}
}
you can change your arrayList subscribed to have only the ids of journals (more optimised).
So, in the controller you can have something like this
ArrayList<Integer> subscribed =
this.subscriberService.getSubscribedJournalsForSubscriber(user); //modify it so it returns the journals ids instead of the whole object(Subscription)
then in the thymeleaf change the anchor with something like this
<a id="href" th:href="'javascript:subscribe(\'' + ${journal.id} + '\');'">
<span th:if="${#lists.contains(subscriptions, journal.id) }" th:text="Unsubscribe"> Unsubscribe </span>
<span th:if="not ${#lists.contains(subscriptions, journal.id) }" th:text="Subscribe"> Subscribe </span>
</a>
Have a look at the documentation of thymeleaf http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html
/*
* Check if element or elements are contained in list
*/
${#lists.contains(list, element)}
${#lists.containsAll(list, elements)}