In my current spring project, I have a generic controller like this:
public class basicController<E> {
#Autowired
private basicService<E> serv;
protected Class<?> clazz;
public basicController(Class<?> clazz) {
this.clazz = clazz;
}
...
}
with a method like this:
#ModelAttribute("lista")
public List<E> populateList() {
return serv.lista();
}
I wonder if it's possible use the value for lista in a structure like that (in the html page):
<select class="form-control" th:name="...">
<option th:each="opt : ${lista}" th:value="${opt.getId()}"><span th:text="${opt}"/>
</option>
</select>
this page is mapped in the controllers with methods like that:
generic controller
#RequestMapping(value = "cadastra")
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
#Menu(label = "cadastra")
public String cadastra(Model model) throws Exception {
model.addAttribute("command", serv.newObject());
return "private/cadastra";
}
home controller (contains mappings for public views, among others things)
#RequestMapping(value = "/settings")
public String settings(Model model) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("usuario", auth.getName());
model.addAttribute("menu", MenuList.index());
model.addAttribute("settings", MenuList.settings());
return "private/settings";
}
#RequestMapping(value = "/profile")
public String profile(Model model) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("usuario", auth.getName());
model.addAttribute("menu", MenuList.index());
model.addAttribute("command", usuario(auth.getName()));
return "private/profile";
}
Anyone have any idea about this?
Ok, I just test and verify no extra configuration is needed for use the value from a ModelAttribute method. So, I just add methods like this in my controller:
#ModelAttribute("lista")
public List<E> populateListPagina() {
return serv.lista();
}
#ModelAttribute("classe")
public String getName() {
return clazz.getSimpleName();
}
and when I access any mapped view, I can use the value returned by this method in the way I like:
<tbody class="content">
<tr th:each="item : ${lista}">
<th></th>
<th th:each="coluna : ${campos}" th:text="${item[coluna]}"></th>
<th>
<div class="btn-group" role="group" aria-label="menu_item">
<button th:each="btn : ${menu_item}" type="button" class="btn btn-default link" th:attr="data-href=#{/__${classe}__/__${btn}__/__${item.getId()}__}" th:text="${btn}"></button>
</div>
</th>
</tr>
</tbody>
#ModelAttribute fires up before your controller methods do, and will disappear once your method runs I believe. So you won't have the object in the view anymore, it acts more like a #RequestParam.
However, you can try adding #SessionAttributes("lista") if you're using newer version of Spring ( I believe 4+). You have to be careful to make sure you close the session attributes though. To close, do what this guy did - link.
Related
I am completely stuck on mapping a controller to a URL. I have searched quite a bit, but cannot figure out what I'm doing wrong, except from the fact that the mapping is just not happening. The parts included are currently the only ones not working.
Controller Code (Part of a bigger controller file with another controller method with value "/course_users")
#RequestMapping(value = "/new_users", method = RequestMethod.GET)
public String addStudent(
Model model,
Principal principal,
HttpServletRequest requestID, HttpServletRequest requestName)
{
Long courseId = Long.parseLong(requestID.getParameter("id"));
User currentUser = userService.findByEmail(principal.getName());
Course currentCourse = courseService.findCourseById(courseId);
model.addAttribute("user", currentUser);
model.addAttribute("courseId", courseId);
try{
if(!currentUser.getRoles().contains(Role.ADMIN) && !currentUser.getRoles().contains(Role.LECTURER)){
String errorMsg = "Nie masz wystarczających uprawnień, aby dodać kursanta";
model.addAttribute("errorMsg", errorMsg);
return "error";
}
List<User> users = userService.findAll();
model.addAttribute("users", users);
String userName = requestName.getParameter("userName");
User newStudent = userService.findByEmail(userName);
courseService.addStudentToCourse(currentCourse, newStudent);
}
catch (IllegalArgumentException e){
String errorMsg = "Podano nieprawidłowe argumenty podczas tworzenia kursu.";
model.addAttribute("errorMsg", errorMsg);
return "error";
}
return "course_users";
}
HTML file code (the file is called "course_users.html")
<div class="container pb-3">
<form th:action="#{/new_users}">
<div class="form-group">
<label for="userName">Dodawany Student:</label>
<select class="form-control" id="userName" th:name="userName">
<option th:each="student : ${users}" th:value="${student.getEmail()}" th:text="${student.getEmail()}"></option>
</select>
</div>
<div class="pt-3">
<button type="submit" class="btn btn-primary">Dodaj studenta</button>
</div>
</form>
EDIT
Relevant part of UserService (Course Service is annotated the same way)
#Service
public class UserService {
private final UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
//rest of service code
}
The error is located in the header, and is attempting to call getFirstName() on user, which it doesn't seem to be getting (and why I'm assuming the controller is not getting mapped).
I had an identical problem (and error) with the first method, where I swapped #PathVriable for HttpServletRequest which fixed the problem, sadly no such luck here.
Method call: Attempted to call method getFirstName() on null context object
I feel like this is most likely caused by something very minor that I just keep missing.
EDIT2
I decided to see what happens if I (once again) try
#RequestParam(value = "userName") String userName and #PathVariable("courseId") Long courseId with value = "/new_users/{courseId}"
That went about the same until I swapped
<option th:each="student : ${users}" th:value="${student.getEmail()}" th:text="${student.getEmail()}"></option>
with
<option th:each="student : ${course.getEnrolledStudents()}" th:value="${student.getEmail()}" th:text="${student.getEmail()}"></option>
which showed me an entry in the selection list and gave me an expected error page on clicking the button! (since I was trying to add the same person a second time)
Could be that I'm just mucking something up in HTML syntax, or using/adding the model variable users wrong?
Your controller looks fine can you check you annotated your courseService and userService with #Autowired annotation or not.
#Autowired
private CourseService courseService
Share your error message if it doesn't solve your problem.
I understand that this way you would access the user's values added to model, having always created the corresponding getters and setters of each attribute:
<select class="form-control" id="userName" th:name="userName">
<option th:each="student : ${users}" th:value="${student.email}"
th:text="${student.email}"></option>
</select>
Finally found and answer!
Instead of trying to add a model attribute through model.addAttribute, just add it like this:
#ModelAttribute("users")
public List<User> users() {
return userService.findAll();
}
I still couldn't tell you why the other method didn't work, but at least here's one that works.
i'm using spring boot and spring MVC. I'm creating a simple form(CRUD)
here is the code:
#Document
public class User
{
#Id
private ObjectId id;
#Indexed(unique=true)
#NotNull
#Size(min=2, max=30)
private String username;
#NotNull
#Size(min=2, max=30)
private String password;
private List<String> roles;
...
Controller:
#Controller
#RequestMapping("/admin")
public class AdminController {
...
/**
* NEW USER (POST)
*/
#RequestMapping(value = "new", method = RequestMethod.POST)
public ModelAndView newUser(#Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return new ModelAndView("/admin/new");
}
user.setRoles(Arrays.asList(Constants.ROLE_ADMIN));
ur.save(user);
return new ModelAndView("redirect:/admin");
}
/**
* NEW USER (VIEW)
*/
#RequestMapping(value = "new", method = RequestMethod.GET)
public ModelAndView newUser(User user) {
ModelAndView mv = new ModelAndView("admin/new");
return mv;
}
...
}
and the View:
<form name="new" th:action="#{/admin/new}" th:object="${user}" method="post">
<table>
<tr th:if="${error != null}">
<td colspan="4">
<span th:text="${error}"></span>
</td>
</tr>
<tr>
<td>Name:</td>
<td><input type="text" name="username" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></td>
</tr>
<tr>
<td><button type="submit">Create</button></td>
</tr>
</table>
</form>
and it works, if I put an username bigger than 30 character.
But if I got and exception from the repository, for example:
DuplicateKey from the mongodb repository, didn't work.
So i tried to put this code in the controller:
#ExceptionHandler(Exception.class)
public ModelAndView handleCustomException(Exception ex) {
ModelAndView model = new ModelAndView("admin/new");
model.addObject("error", ex.getMessage());
return model;
}
It handle all the exceptions, but in this moment I don't have the "User" or "BindingResult" and when it try to render gets this error:
2015-09-22 13:36:55.498 ERROR 6208 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('username')" (admin/new:21)] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
What I'm doing wrong?
How should I handle this kind of exception?
There is a way to send the USER to ExceptionHandler?
Thanks.
Because you don't have to return admin/new from the error handler. The view is expecting the User object that you are correctly populating in normal controllers. In case of an error this object simply isn't there. So, return another view from the error controller: ModelAndView model = new ModelAndView("admin/error"); for example.
Anyhow, this is not a solution. You need to catch exceptions. In this situation it is always better to have an intermediate layer called Service. In service you can catch any repository exceptions and decide what to do about them.
I'm trying to work through a simple example of Spring Boot and FreeMarker integration (based on tutorials I've found on the web). For some reason my view is not being resolved to the FreeMarker template (I think that's the issue).
The result when launched in a browser is simply to return the name of the TFL view file i.e. "index". So the controller is being called and returning the string "index", but there seems to be no trigger to pull in the FTL file itself. Any help would be appreciated...
I have the following configuration class where I define the view resolver and the Free Maker config.
#Configuration
public class MvcConfigurer extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html; charset=UTF-8");
return resolver;
}
#Bean
public FreeMarkerConfigurer freemarkerConfig() throws IOException, TemplateException {
FreeMarkerConfigurationFactory factory = new FreeMarkerConfigurationFactory();
factory.setTemplateLoaderPaths("classpath:templates", "src/main/resource/templates");
factory.setDefaultEncoding("UTF-8");
FreeMarkerConfigurer result = new FreeMarkerConfigurer();
result.setConfiguration(factory.createConfiguration());
return result;
}
}
Then I have the following controller:
#RestController
public class HelloController {
/**
* Static list of users to simulate Database
*/
private static List<User> userList = new ArrayList<User>();
//Initialize the list with some data for index screen
static {
userList.add(new User("Bill", "Gates"));
userList.add(new User("Steve", "Jobs"));
userList.add(new User("Larry", "Page"));
userList.add(new User("Sergey", "Brin"));
userList.add(new User("Larry", "Ellison"));
}
/**
* Saves the static list of users in model and renders it
* via freemarker template.
*
* #param model
* #return The index view (FTL)
*/
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(#ModelAttribute("model") ModelMap model) {
model.addAttribute("userList", userList);
return "index";
}
/**
* Add a new user into static user lists and display the
* same into FTL via redirect
*
* #param user
* #return Redirect to /index page to display user list
*/
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String add(#ModelAttribute("user") User user) {
if (null != user && null != user.getFirstname()
&& null != user.getLastname() && !user.getFirstname().isEmpty()
&& !user.getLastname().isEmpty()) {
synchronized (userList) {
userList.add(user);
}
}
return "redirect:index.html";
}
}
Then finally I have the following FTL file stored in "src/main/resource/templates"
<html>
<head><title>ViralPatel.net - FreeMarker Spring MVC Hello World</title>
<body>
<div id="header">
<H2>
<img height="37" width="236" border="0px" src="http://viralpatel.net/blogs/wp-content/themes/vp/images/logo.png" align="left"/>
FreeMarker Spring MVC Hello World
</H2>
</div>
<div id="content">
<fieldset>
<legend>Add User</legend>
<form name="user" action="add.html" method="post">
Firstname: <input type="text" name="firstname" /> <br/>
Lastname: <input type="text" name="lastname" /> <br/>
<input type="submit" value=" Save " />
</form>
</fieldset>
<br/>
<table class="datatable">
<tr>
<th>Firstname</th> <th>Lastname</th>
</tr>
<#list model["userList"] as user>
<tr>
<td>${user.firstname}</td> <td>${user.lastname}</td>
</tr>
</#list>
</table>
</div>
</body>
</html>
The problem is that your controller has the wrong annotation.
You should use #Controller instead of #RestController
#RestController is used to tell that the response sent from your controller should be sent to the browser, usually an object mapped to json.
It is the same as adding #ResponseBody.
Although you just got the answer. However, your post has two points.
Firstly, configure Freemarker template in Spring Boot quite easy. No need to use WebMvcConfigurerAdapter. You just need to place your properties on your class path with the content below
spring.freemarker.template-loader-path: /templates
spring.freemarker.suffix: .ftl
Secondly, #Controller is used to annotated classes as Spring MVC Controller. #RestController annotated classes are the same as #Controller but the #ResponseBody on the handler methods are implied. So you must use #Controller in your case.
Found this from the post Spring Boot FreeMarker Hello World Example
I'm building simple twitter clone in Spring MVC. I want to provide edit functionality to posted messages.
Message domain object looks like this (simplified)
public class Message {
long id;
String text;
Date date;
User user;
}
I created jps form
<form:form action="edit" method="post" modelAttribute="message">
<table>
<tr>
<td><label for="text">Message: </label></td>
<td><form:textarea path="text" id="text"/></td>
</tr>
<tr>
<td><input type="submit" name="commit" value="Save" /></td>
</tr>
</table>
</form:form>
and added those method in controller class
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String showEditMessage(#RequestParam long id, Model model) {
Message message = messageService.findMessage(id);
if (message == null) {
return "404";
}
model.addAttribute("message", message);
return "users/editMessage";
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
public String editMessage(#Valid #ModelAttribute Message message, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/users/editMessage";
}
messageService.updateMessage(message);
return "/users/editMessage";
}
The problem is that the Message received in editMessage() contains only text field. I assume that this is expected behaviour. Can it be configured to replace fields that are only in jsp form?
I know this is only one field and I could just use #RequestParam String message, but sooner or later I will face similar problem with more than just one field.
I also have side question.
Are attributes added in showEditMessage() are passed to editMessage() method? I tried to add "id" attribute in first method, but I couldn't retrive it using "#RequestParam long id" in second.
#SessionAttributes("message")
On top of controller class solved it.
I am using Spring form to get inputs from client (if i use normal html input). If i use Spring form input i got error : java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'enumLanguage' available as request attribute
this is my JSP:
<form:form commandname="enumLanguage" action="${pageContext.request.contextPath}/enumLanguage/create.action" method="post" modelAttribute="enumLanguage" >
<fieldset class="langStep">
<legend>Language Details</legend>
<table class="langpadding">
<tr>
<td><label>Name:</label></td>
<td><form:input path="name" cssClass="textbox2"></form:input></td>
<td><label class="llangpadding">Short Name:</label></td>
<td><form:input path="shortName" cssClass="textbox2"></form:input></td>
</tr>
</table>
Save<span class="icon icon3"></span>
</form:form>
and this is my Controller:
#RequestMapping( value="/enumLanguage/create.action", method=RequestMethod.POST)
public ModelAndView create(#ModelAttribute EnumLanguage enumLanguage) throws Exception {
ModelAndView mvc = null;
try{
List<EnumLanguage> enumLanguages = new ArrayList<EnumLanguage>();
enumLanguages.add(enumLanguage);
List<EnumLanguage> enumLanguagesList = enumLanguageService.create(enumLanguages);
mvc = new ModelAndView("setup/EnumLanguageList");
} catch (Exception e) {
}
return mvc;
}
Make sure you have your #ModelAttribute set to the model when rendering the view
Make sure you made available in the view a model attribute with a key enumLanguage which is the value of the commandname of the form.
So the controller method that returns the view containing the form that you posted should look something like this.
#RequestMapping(value = "/language-details.do", method = RequestMethod.GET)
public ModelAndView initLanguageDetailsView() {
ModelMap model = new ModelMap();
EnumLanguage enumLang = new EnumLanguage();
//setters blah blah
//...
//make it available to the view
model.addAttribute("enumLanguage", enumLang);
return new ModelAndView("language-details", model);
}