A problem occurs when I try to make POST request by an html page using thymeleaf. A controller should receive an input as an enum, but it throws an error:
java.lang.NoSuchMethodException: com.trade_analysis.model.StockSymbol.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3427) ~[na:na]"
I don't know what's not ok. I have seen some examples and have tried many things but I can't make it work as it should.
HTML:
<select class="form-item" th:field="${symbol}" required>
<option value="" selected disabled hidden id="default-symbol">Symbol</option>
<option class="dropdown-menu-button" th:each="symbolOption: ${symbols}" th:value="${symbolOption}" th:text="${symbolOption}"></option>
</select>
Java controler:
#GetMapping(value = "/stocks")
#PreAuthorize(value = "isAuthenticated()")
public String getStockPrices(Model model) throws UserNotFoundException {
User user = userService.getUserByUsername(getUsername());
String apiKey = user.getApiKey() == null ? "" : user.getApiKey();
model.addAttribute("apiKey", apiKey);
model.addAttribute("symbol", "");
model.addAttribute("symbols", asList(StockSymbol.values()));
return "stock-prices-preview";
}
#PostMapping(value = "/stocks")
#PreAuthorize(value = "isAuthenticated()")
public String stockPrices(#ModelAttribute String apiKey, #ModelAttribute StockSymbol symbol, Model model) {
model.addAttribute("apiKey", apiKey);
model.addAttribute("symbol", symbol);
model.addAttribute("symbols", asList(StockSymbol.values()));
return "stock-prices-preview";
}
'StockSymbol' enum:
public enum StockSymbol {
GOOGL("GOOGL"),
MSFT("MSFT"),
AMZN("AMZN"),
IBM("IBM"),
CSCO("CSCO"),
AAPL("AAPL");
String sys;
StockSymbol(String sys) {
this.sys = sys;
}
}
First few lines of error (full error is on: https://pastebin.com/kg8RR7G6)
java.lang.NoSuchMethodException: com.trade_analysis.model.StockSymbol.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3427) ~[na:na]
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2631) ~[na:na]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:216) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
In your POST handler you have this line:
public String stockPrices(#ModelAttribute String apiKey, #ModelAttribute StockSymbol symbol, Model model) {
Remove the #ModelAttribute annotation. Your problem is that Spring tries to instantiate the enum when it runs the controller method.
Use #RequestParam to obtain the incoming POST parameter by name. You might need to specify the parameter name in the annotation if your compiler doesn't use the -parameters switch.
Related
I am trying to write a search function, but I encounter a bug when I pass the search query from frontend to backend. I tried most of the solution on Internet but it's still not ok.
Complete Error log
2022-10-12 15:05:10.575 WARN 21272 --- [nio-8090-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'searchQuery' for method parameter type String is not present]
frontend
<template>
<div class="input-group mb-3">
<input type="search" class="form-control rounded" v-model="searchQuery" placeholder="Company name" aria-label="Search" aria-describedby="search-addon" />
<button type="button" class="btn btn-outline-primary" #click='searchRecord'>Search</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'RegistrationEnquiry',
components: {
},
data() {
return {
records: [],
searchQuery: '',
};
},
computed: {},
methods: {
searchRecord(){
axios.post('searchRecord', this.searchQuery)
.then(successResponse => {
console.log(successResponse)
})
.catch(failResponse => {
alert("Error(failResponse)")
console.log(failResponse)
})
},
},
}
</script>
SearchRecordController.java
#Controller
public class SearchRecordController {
#Autowired
SearchRecordService searchRecordService;
#CrossOrigin
#PostMapping(value = "api/searchRecord")
#ResponseBody
public String searchRecord(#RequestParam(value = "searchQuery") String searchQuery) {
System.out.println(searchQuery);
return searchRecordService.searchRecordService(searchQuery);
}
}
It is not the correct way of sending parameters through axios.
You can send params by changing your code in frontend to :-
axios.post(`api/searchRecord`, null, { params: {
searchQuery
}}
Which will send the request as :
https://localhost:8080/api/searchRecord?searchQuery=valueofsearchtext
Keep your controller as it is as there are no changes required in the backend.
#CrossOrigin
#PostMapping(value = "api/searchRecord")
#ResponseBody
public String searchRecord(#RequestParam(value = "searchQuery") String searchQuery) {
System.out.println(searchQuery);
return searchRecordService.searchRecordService(searchQuery);
}
this should sort the issue in your code.
From backend side it seems ok, I think you need to send data in post like that:
searchRecord(){
axios.post({
method: 'post',
url: '/api/searchRecord',
data: {
searchQuery: this.searchQuery
}
})
.then(successResponse => {
console.log(successResponse)
})
.catch(failResponse => {
alert("Error(failResponse)")
console.log(failResponse)
})
}
basically, it appears when you send no value from the front end but your controller expects some parameter from the frontend side,
to avoid this u can use "required = false" or u can fix a default value based on your requirement to check where you are going wrong.
public String searchRecord(#RequestParam(value = "searchQuery",required = false,defaultValue = "") String searchQuery) {
return searchRecordService.searchRecordService(searchQuery);
}
Front end is not sending data to the controller. Try giving default value=null;
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 have an ApplicationAdvice class that passes a reference to a profile picture on every page:
#ControllerAdvice
public class ApplicationAdvice {
...
#ModelAttribute("currentProfilePicture")
public String currentProfilePicture(Principal principal, Model model) {
if (principal != null) {
String currentProfilePicture = "#{/images/default-profile-picture.png}";
log.info(currentProfilePicture);
model.addAttribute("currentProfilePicture", currentProfilePicture);
return currentProfilePicture;
} else {
String currentProfilePicture = "#{/images/default-profile-picture.png}";
log.info(currentProfilePicture);
model.addAttribute("currentProfilePicture", currentProfilePicture);
return currentProfilePicture;
}
}
}
HTML:
<img class="profilePicture hvr-pulse-grow" th:src="${currentProfilePicture}" />
Am I not escaping the static reference properly? #{/images/default-profile-picture.png} The url string prints out fine... I basically just want to pass a string to a static file to the img tag.
It should look like this:
String currentProfilePicture = "/images/default-profile-picture.png";
<img class="profilePicture hvr-pulse-grow" th:src="#{${currentProfilePicture}}" />
You can't pass an entire thymeleaf string to be evaluated without doing some tricks with the preprocessing, but the above should do what you want.
I've got a form with a dropdown:
<div class="form-group">
<form:label path="departments">Dept. Code</form:label>
<form:select path="departments" items="${departmentMap}" multiple="true" />
departmentMap comes from the controller method:
#RequestMapping(value = "/officeForm", method=RequestMethod.GET)
public ModelAndView showOfficeForm() {
ModelAndView result = new ModelAndView("officeForm", "command", new Office());
List<Department> departmentsToDisplay = departmentServiceImpl.findAll();
Map<Department, String> departmentMap = new HashMap<Department, String>();
for (Department d : departmentsToDisplay) {
departmentMap.put(d, d.getDepartmentName());
}
result.addObject("departmentMap", departmentMap);
return result;
}
POST method:
#RequestMapping(value = "/addOffice", method = RequestMethod.POST)
public ModelAndView updateOffice(#ModelAttribute("office") Office office, BindingResult result) {
System.out.println("Office Name: " + office.getOfficeName());
System.out.println("Departments: " + office.getDepartments());
return new ModelAndView("result", "command", office);
}
Excerpt from Office.java:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "offices")
private List<Department> departments;
Excerpt from Department.java:
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinTable(name="OFF_DEPT_T",
joinColumns={#JoinColumn(name="DEPT_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="OFF_ID", referencedColumnName="ID")}
)
private List<Office> offices = new ArrayList<Office>();
If I print the response.getAllErrors() I get:
Field error in object 'office' on field 'departments': rejected value [package.domain.Department#5597e5cf,package.domain.Department#2d14d0a7]; codes [typeMismatch.office.departments,typeMismatch.departments,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [office.departments,departments]; arguments []; default message [departments]]; default message [Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property 'departments'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [package.domain.Department] for property 'departments[0]': no matching editors or conversion strategy found]
Can anybody show me how to pass the object but display the String? Thanks.
Problem 1:
As you are missing initialisation of List departments. You should initialise it before putting it in model by replacing:
ModelAndView result = new ModelAndView("officeForm", "command", new Office());
with:
Office office = new Office():
office.setDepartments(new ArrayList<Department>()):
ModelAndView result = new ModelAndView("officeForm", "command", office);
Or if you don't want initialisation in controller, you can initialise it at the time of creation of Office object, like below:
private List<Department> departments = new ArrayList<Department>();
Problem 2:
As you want to bind custom object (Department) list in your select path, you need to provide a custom Property Editor to the data binder, like below:
First create a Property Editor class, something like this:
public class DepartmentEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
Department department = new department();
department.setName(text);
setValue(department);
}
}
Then register the property Editor by registering it. You can register it, by simply putting an initBinder method in your controller class like below:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Department.class, new DepartmentEditor());
}
Now, your code should work fine.
I bound command to a new office attribute.
#RequestMapping(value = "/officeSearch", method=RequestMethod.GET)
public String showOfficesSearch(Model model) {
model.addAttribute("command", new Office());
return "officeSearch";
}
Hi I received next error during the redirect:
The request sent by the client was syntactically incorrect
URL which browser shows is: localhost:8080/Project/menu/main/home/0 and here my classes with redirects first - "from", second "to":
/*
* Get all possible values of menu and generate correct url to pages controllers
*
*/
#Controller
#SessionAttributes("menu")
public class MainMenuController {
#ModelAttribute
public Menu createMenu() {
return new Menu();
}
#RequestMapping(value = "/menu", method = RequestMethod.GET)
public String mainMenuResolver(#ModelAttribute Menu menu) {
menu.setMainMenu("first");
return "forward:/menu/first";
}
#RequestMapping(value = "/menu/{mainMenu}", method = RequestMethod.GET)
public String subMenuResolver(#PathVariable String mainMenu, #ModelAttribute Menu menu) {
menu.setMainMenu(mainMenu);
menu.setSubMenu("home");
return "forward:/menu/first/home";
}
#RequestMapping(value = "/menu/{mainMenu}/{subMenu}", method = RequestMethod.GET)
public String secMenuResolver(#PathVariable String mainMenu, #PathVariable String subMenu, #ModelAttribute Menu menu) {
menu.setMainMenu(mainMenu);
menu.setSubMenu(subMenu);
menu.setSecMenu("0");
if (menu.getMainMenu().equals("first")){
return "redirect:/menu/main/"+menu.getSubMenu()+"/"+menu.getSecMenu();
}
if (menu.getMainMenu().equals("second")){
return "redirect:/menu/religion/"+menu.getSubMenu()+"/"+menu.getSecMenu();
}
return "redirect:/menu/main/"+menu.getSubMenu()+"/"+menu.getSecMenu();
}
}
Second class:
#Controller
#SessionAttributes("menu")
public class FirstPageController {
#ModelAttribute
public Menu createMenu() {
return new Menu();
}
#RequestMapping(value = "/menu/main/{subMenu}/{secMenu}", method = RequestMethod.GET)
public ModelAndView menuResolver(#PathVariable String mainMenu, #PathVariable String subMenu,#PathVariable String secMenu, #ModelAttribute("menu") Menu menu) {
menu.setMainMenu(mainMenu);
menu.setSubMenu(subMenu);
menu.setSecMenu(secMenu);
if (menu.getSubMenu().equals("home")){
String title = "Project - Home Page";
return new ModelAndView("MainPage", "title", title);
}
String title = "Project - Home Page";
return new ModelAndView("MainPage", "title", title);
}
}
Solved: I solved it, there excess parameter in the method of the second class.
In cases like this it is very useful to have org.springframework.web loggin level set to DEBUG in log4j configuration
<logger name="org.springframework.web">
<level value="DEBUG" />
...
</logger>
E.g. when parameter is missing or cannot be converted to the required type there will be an exception details in the log.
In my case the reason of this error was that browser (Chrome, in my particular case) was sending the date from the <input type="date" ... /> to the server in the wrong format so server didn't know how to parse it.
As said ike3, using the detailed log aided a lot to find the solution for me. In my case it was a mismatch between #PathVariable without name specified, and the variable itself.
Something like this:
#RequestMapping("/user/{uname}")
public String doSomething(#PathVariable String username) { ...
Note the difference between "uname" and "username" !
There was an exception internally that wasn't raised and I couldn't see it until I set the log to INFO level.
In my case, it was also a problem of conversion, Spring was expecting an Integer however I was entering a String. Try to check what you have passed as parameters to the controller