I'm writing my first spring boot app and I'm stuck with this problem. I can't show error message to user. Object without that data is not saved in the database and that is OK. But showing error message is the problem. When I debug i get errors size = 0
This is model
#Size(min = 1, message = "Address is invalid.")
#NotNull
#Column
private String address;
Controller
#RequestMapping(value = "/create", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String createNewBusiness(#Valid #ModelAttribute("business") Business business,
BindingResult result, Model model) {
model.addAttribute("userEmail", getUserEmail());
logger.info("/business/create:" + business.toString());
LocationResponse locationResponse = geoService.getCoords(business.getAddress());
if (locationResponse.getStatus().equals("OK")) {
business.setLatitude(locationResponse.getResults().get(0).getGeometry().getLocation().getLat());
business.setLongitude(locationResponse.getResults().get(0).getGeometry().getLocation().getLng());
business.setUserId(getUserId());
businessService.createNew(business);
model.addAttribute("business", business);
} else {
business.setAddress(null);
model.addAttribute("business", business);
}
if (result.hasErrors()) {
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors ) {
System.out.println (error.getObjectName() + " - " + error.getDefaultMessage());
}
return "newBusiness";
}
return "business";
}
Thymeleaf
<div class="input-field left m-0 w-100">
<i class="fa fa-map-marker prefix grey-text" aria-hidden="true"></i>
<input placeholder="Address" id="inputAddress" name="address" type="text" class="validate my-0" th:required="true">
<label th:errors="*{address}" th:if="${#fields.hasErrors('address')}" >Invalid address</label>
</div>
Did you define a Validator in your #SpringBootApplication?
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
}
Related
Hi guys new to spring and was wondering why this does work like I though it would. So I have a project model which has a name(a string) and a user(a user object). So I store it mysql and want to retrieve a project base on those two fields and i want to display the project on page which I did already(but only base on user). So add a new thing where I'm using a search bar for input for name. So when the page first load, there isn't any input so it should just retrieve all projects base of user. So I made name (required = false). However, it doesn't show all the projects only base on user(Nothing shows). It only something shows when I enter like a name into search bar.
<form class="form-inline mt-5 my-lg-0" action="/myWork">
<input type="text" class="form-control" name="name" placeholder="Search Project" />
<input type="submit" value="Search" class="btn btn-primary"/></form>
public interface ProjectRepository extends JpaRepository<Project, Integer> {
#Override
List<Project> findAll();
public List<Project> findByUser(User user);
public List<Project>findByUserAndName(User user,String name);
public List<Project> findByType(String type);
}
//Get a project base on user and project name
public List<Project> getProjectByUserandName(Authentication authentication,String name) {
User user = authenticationService.getPrincipal(authentication);
return projectRepo.findByUserAndName(user,name);
}
#GetMapping("/myWork")
public ModelAndView showUserProject(Authentication authentication, #RequestParam(required = false) String name) {
ModelAndView modelAndView = new ModelAndView();
List<Project> projects = new ArrayList<Project>();
try {
projects = projectService.getProjectByUserandName(authentication,name);
System.out.println(projects);
Collections.sort(projects, new customComparator());
} catch (Exception e) {
e.printStackTrace();
}
modelAndView.addObject("projects", projects);
return modelAndView;
}
Handle Empty case of name in getProjectByUserandName.
Instead of
return projectRepo.findByUserAndName(user,name);
Use this :
if(StringUtils.isEmpty(name))
return projectRepo.findByUser(user);
else
return projectRepo.findByUserAndName(user,name);
I dont know how to pass an error to thymeleaf outside a form and not specific to a field. Just a small check if the book is on stock. If yes, process everything. If not, throw a error message on the client side.
Controller
#PostMapping("/books/borrow/{id}")
public String borrowBook(#PathVariable int id, Errors errors) {
Book borrowedBook = bookRepository.findById(id);
if (borrowedBook.getAvailableCount() == 0){
errors.rejectValue("onStock", "Book out of stock. Come later...");
return "redirect:/books/";
} else{...}
View
<p th:text="${onStock}"></p>
I don't undestand how to pass the parameter and show it to the client. I did research https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#validation-and-error-messages
but they are all specific to a form and field.
What am i doing wrong?
You can use a RedirectAttribute to achieve that:
Controller
#PostMapping("/books/borrow/{id}")
public String borrowBook(#PathVariable int id, Errors errors, RedirectAttributes redirectAttributes) {
Book borrowedBook = bookRepository.findById(id);
if (borrowedBook.getAvailableCount() == 0){
errors.rejectValue("onStock", "Book out of stock. Come later...");
redirectAttributes.addFlashAttribute("errorMessage", "We couldn't process your order!");
return "redirect:/books/borrow/" + id;
} else {
//Process the request
}
Template (HTML)
<div th:if="${errorMessage}">
<div th:text="${errorMessage}"></div>
</div>
#PostMapping("/books/borrow/{id}")
public String borrowBook(#PathVariable int id, Errors errors) {
Book borrowedBook = bookRepository.findById(id);
if (borrowedBook.getAvailableCount() == 0){
errors.rejectValue("onStock", "Book out of stock. Come later...");
return "redirect:/books/?errors="+errors.getAllErrors();
}
on get request mapping
#GetMapping("/books")
public String getBooks(Model model, #RequestParam("errors") List<ObjectError> errors)
{
model.addAttribute("errors", errors);
return null;
}
I try to work with date fields in Freemarker.
Here is my controller methods for creating Account objects:
#GetMapping(value = "/accounts/add")
public String showAddAccount(Model model) {
Account account = new Account();
model.addAttribute("add", true);
model.addAttribute("account", account);
return "account-edit";
}
#PostMapping(value = "/accounts/add")
public String addAccount(
Model model,
#ModelAttribute("account") Account account
) {
try {
Account newAccount = accountService.save(account);
return "redirect:/accounts/" + newAccount.getId();
} catch (Exception e) {
String errorMessage = e.getMessage();
logger.error(errorMessage);
model.addAttribute("errorMessage", errorMessage);
model.addAttribute("add", true);
return "account-edit";
}
}
It is part of my Freemarker template, where I am formatting date:
<#if account.createdOn??>
<tr>
<td>Created On</td>
<td>:</td>
<td>${(account.createdOn).format('yyyy-MM-dd HH:mm:ss')}</td>
</tr>
<tr>
<td>Updated On</td>
<td>:</td>
<td>${(account.updatedOn).format('yyyy-MM-dd HH:mm:ss')}</td>
</tr>
</#if>
I have added dependency:
<dependency>
<groupId>no.api.freemarker</groupId>
<artifactId>freemarker-java8</artifactId>
<version>1.3.0</version>
</dependency>
I found this dependency to use Data/Time in Java 8 in template.
After I added config class for freemarker:
#Configuration
public class FreemarkerConfig implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
configurer.getConfiguration().setObjectWrapper(new Java8ObjectWrapper(freemarker.template.Configuration.getVersion()));
}
return bean;
}
}
I am receiving an error "Column 'created_on' cannot be null" and:
could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
I am writing unit tests for my code. Now I want to test if a value put into the form is correctly saved in the variable in the Controller. Both tests that depend on this model attribute being correct, fail. Because the model exists but stays null, this must mean I'm sending the value from my test the wrong way. How can I have my test include an entered value to test the post method correctly?
The test testPostValueInModel() fails with an AssertionError:
java.lang.AssertionError: Model attribute 'chosenTemp' does not exist
I must note that I'm pretty new to all this, so if anyone has an answer please provide some more code examples and explain what is going wrong so I can learn from my mistakes. Thank you.
Here's my test class:
#RunWith(SpringRunner.class)
#WebMvcTest(InvoerschermController.class)
#AutoConfigureMockMvc
public class InvoerschermTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testCorrectModel() {
try {
this.mockMvc.perform(get("/invoer", "20")).andExpect(status().isOk())
.andExpect(model().attributeExists("chosenTemp"));
} catch (Exception e) {
e.printStackTrace();
}
}
#Test
public void testPost() {
try {
this.mockMvc.perform(post("/invoer", "20")).andExpect(status().isOk())
.andExpect(view().name("invoerscherm"));
} catch (Exception e) {
e.printStackTrace();
}
}
#Test
public void testPostValueInModel() {
try {
this.mockMvc.perform(post("/invoer", "20")).andExpect(status().isOk())
.andExpect(model().attributeExists("chosenTemp"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
The Controller:
#Controller
public class InvoerschermController {
private String chosenTemp = "20";
private static PostgresDatabase database;
private static Connection connection;
// Static initializer for the database
static {
database = new PostgresDatabase();
connection = database.connectToDatabase();
}
#GetMapping("/invoer")
public String invoer(Model model) {
// int newTemp = Integer.parseInt(getChosenTemp());
chosenTemp = database.getTemperature(connection);
model.addAttribute("chosenTemp", getChosenTemp());
return "invoerscherm";
}
#PostMapping("/invoer")
public String addInputTemp(String chosenTemp, Model model) {
setChosenTemp(chosenTemp);
model.addAttribute("chosenTemp", getChosenTemp());
try {
int newTemp = Integer.parseInt(getChosenTemp());
database.setTemperature(connection, newTemp);
} catch (NumberFormatException nfe) {
System.err.println("Invalid number: " + nfe.getMessage());
}
return "invoerscherm";
}
public String getChosenTemp() {
return chosenTemp;
}
public void setChosenTemp(String chosenTemp) {
this.chosenTemp = chosenTemp;
}
}
The Thymeleaf:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="fragments/template :: head"></head>
<head>
<title>Smart CV</title>
</head>
<body>
<nav th:replace="fragments/template :: header"></nav>
<div class="container">
<div class="hero-unit">
<h1>Temperatuur instellen</h1>
</div>
<form action="#" th:action="#{/invoer}" th:object="${invoerscherm}"
method="post">
<div class="form-group">
<label for="chosenTemp">Gewenste temperatuur:</label> <input
type="text" class="form-control" id="chosenTemp" name="chosenTemp"
autocomplete="off" th:value="${chosenTemp}" />
</div>
<button type="submit" class="btn btn-default" name="submitKnop">Stel
in</button>
</form>
</div>
<nav th:replace="fragments/template :: footer"></nav>
</body>
</html>
First of all your controller is flawed. You shouldn't keep local state (try to imagine what happens to the chosenTemp field when 3 users submit at the same time as there is only a single instance of the InvoerschermController.
Your method argument should be annotated with #RequestParam("chosenTemp") to match the form you are sending. Your test should also reflect the fact that you are sending a parameter named chosenTemp.
First your controller
#Controller
public class InvoerschermController {
private static PostgresDatabase database;
private static Connection connection;
// Static initializer for the database
static {
database = new PostgresDatabase();
connection = database.connectToDatabase();
}
#GetMapping("/invoer")
public String invoer(Model model) {
Integer chosenTemp = database.getTemperature(connection);
model.addAttribute("chosenTemp", chosenTemp);
return "invoerscherm";
}
#PostMapping("/invoer")
public String addInputTemp(#RequestParam("chosenTemp") Integer chosenTemp, Model model) {
model.addAttribute("chosenTemp", chosenTemp);
database.setTemperature(connection, chosenTemp);
return "invoerscherm";
}
}
Notice the type change from String to Integer Spring will do the type conversion for you and notice the addition of the #RequestParam. Now your test should also reflect this.
#RunWith(SpringRunner.class)
#WebMvcTest(InvoerschermController.class)
#AutoConfigureMockMvc
public class InvoerschermTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testCorrectModel() {
try {
this.mockMvc.perform(get("/invoer")).andExpect(status().isOk())
.andExpect(model().attributeExists("chosenTemp"));
} catch (Exception e) {
e.printStackTrace();
}
}
#Test
public void testPost() {
try {
this.mockMvc.perform(post("/invoer").param("chosenTemp", "20").andExpect(status().isOk())
.andExpect(view().name("invoerscherm"));
} catch (Exception e) {
e.printStackTrace();
}
}
#Test
public void testPostValueInModel() {
try {
this.mockMvc.perform(post("/invoer").param("chosenTemp", "20")).andExpect(status().isOk())
.andExpect(model().attributeExists("chosenTemp"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Notice the addition of .param("chosenTemp", "20") to add a request parameter with that name.
Your controller is still flawed imho as it shouldn't care about the Connection that should all be encapsulated in your Database class. Although your test now probably works your actual application will still fail due to the use of Thymeleaf and form binding. The form binding expects an object under the key invoerScherm to be available and that object should have a property named chosenTemp. You are actually lacking a form object. So what your controller should actually look like.
First you need a form object:
public class InvoerScherm {
private Integer chosenTemp;
public InvoerScherm() {}
public InvoerScherm(Integer temp) { this.chosenTemp=temp;}
// Here be getters/setters
}
Then let your controller create and use it
#Controller
public class InvoerschermController {
private static PostgresDatabase database;
private static Connection connection;
// Static initializer for the database
static {
database = new PostgresDatabase();
connection = database.connectToDatabase();
}
#GetMapping("/invoer")
public String invoer(Model model) {
Integer chosenTemp = database.getTemperature(connection);
InvoerScherm invoerScherm = new InvoerScherm(chosenTemp);
model.addAttribute("invoerScherm", invoerScherm);
return "invoerscherm";
}
#PostMapping("/invoer")
public String addInputTemp(#ModelAttribute InvoerScherm invoerScherm, Model model) {
database.setTemperature(connection, invoerScherm.getChosenTemp());
return "invoerscherm";
}
}
Ofcourse now your test will fail again, but I leave that task to you.
Can anyone teach me or direct to a working example to satisfy this requirement.
Scenario:
List item My Web App is using spring mvc.
One of the services it provides is that when the user clicks on a button a long running process will occur on the server. (Query database, write files, write logs, etc...) this process can take a few seconds or a few minutes.
*Problem***
How can I implement the service to update the client of its progress.
The service returns true or false if the process was successful.
Thanks for your replies. A code snippet or a complete tutorial will be most helpful.
Here is a possible solution to this progress bar problem:
task.jsp
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<%#taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<script src="../js/jquery.min.js"></script>
<script>
$(document).ready(function () {
$.getJSON(window.location.href.concat('/status'), function(data) {
if (data === "created") {
} else {
// task is already being executed
refreshProgress();
}
});
});
var width = 0;
function getProgress() {
$.getJSON(window.location.href.concat('/progress'), function(percentage) {
$('#progressBar').css('width', percentage+'%');
document.getElementById("label").innerHTML = percentage * 1 + '%';
width = percentage;
});
}
function start() {
$.ajax({
type: "post",
data: $('#task').serialize(),
success: function(data) {
$('#progressBar').css('width', 100+'%');
document.getElementById("label").innerHTML = 100 * 1 + '%';
// do sth with the data after finished task
}
});
width = 0;
$('#progressBar').css('width', 0+'%');
document.getElementById("label").innerHTML = 0 * 1 + '%';
refreshProgress();
}
function refreshProgress() {
$("#btnStart").prop("disabled",true);
var id = setInterval(frame, 1000);
function frame() {
if (width >= 100) {
clearInterval(id);
$("#btnStart").prop("disabled",false);
} else {
getProgress();
}
}
}
</script>
</head>
<body>
<div class="container">
<h2 class="text-center">Progress Bar Example</h2>
<div class="progress">
<div id="progressBar" class="progress-bar" role="progressbar" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100" style="width:0%">
<div id="label">0%</div>
</div>
</div>
<form:form method="POST" commandName="task" cssClass="form-horizontal">
<fieldset>
<div class="form-group">
<label class="col-md-4 control-label" for="btnStart">Actions</label>
<div class="col-md-8">
<button id="btnStart" name="btnStart" class="btn btn-success">Start</button>
<button id="btnStop" name="btnStop" class="btn btn-danger">Stop</button>
</div>
</div>
</fieldset>
</form:form>
</div>
<script>
$('#task').submit(function () {
start();
return false;
});
</script>
</body>
</html>
TaskController.java
#Controller
#RequestMapping(value = "/task")
public class TaskController {
private Task task;
#RequestMapping("")
protected ModelAndView page() {
ModelAndView model = new ModelAndView(VIEW_DIR + "task");
if (this.task == null) {
this.task = new Task();
}
model.addObject("task", this.task);
return model;
}
#RequestMapping(value = "/status", method = GET)
public #ResponseBody
String getStatus() {
return task.getStatus();
}
#RequestMapping(value = "/progress", method = GET)
public #ResponseBody
int getProgress() {
return task.getProgress();
}
public ModelAndView form(#ModelAttribute Task task) {
this.task = task;
ModelAndView model = new ModelAndView(VIEW_DIR + "task");
task.execute();
model.addObject("task", this.task);
return model;
}
}
Task.java
public class Task {
private int total;
private int progress;
private String status;
public Task() {
this.status = "created";
// TODO get total here or pass via form
}
public void execute() {
status = "executing";
int i = 0;
while (i < total && status.equals("executing")) {
progress = (100 * (i + 1) / total);
i++;
}
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
There are a good number of ways to handle a scenario like this. One way is to model the work in terms of a "Process", which contains a "status", including a percentage completion.
If you imagine what this might look like on a website, clicking the button to start the process would submit a form that begins the process and assigns some sort of identity to the process, almost like if you were creating any other sort of object. It would then redirect you to a "process status" page.
The process status page would query for the status of the process and display it. It'd probably have a URL parameter for the process's ID. It would perhaps update itself using an AJAX call to return a progress percentage.
On the backend, you now need to solve a couple of problems: finding out the current status of process N, and updating the status of process N. You could accomplish this in a number of ways, including storing the progress in the database or having some sort of in-memory table of running jobs. You could also use some sort of heuristic to estimate a percent. For example, if it's a "register new user" job, maybe it's 20% done if the user's table has an email address, 40% done if the user avatar table has data in it for this user, etc. I don't recommend this as much.