Why a Spring MVC model attribute is not (always) accessible in the model? - spring

After reading questions and answers on the topic, such as
Explanation of the model object in Spring
as well as the official documentation of Spring MVC, I still wonder why is a Spring MVC model attribute is not (always) accessible in the model. For example in my code below you can see a Spring MVC Controller class:
#Controller
#RequestMapping("/student")
public class StudentController {
#RequestMapping("/showForm")
public String showForm(Model model) {
Student theStudent = new Student();
model.addAttribute("student", theStudent);
return "student-form";
}
#RequestMapping("/processFormV1")
public String processStudentInput(#ModelAttribute("student") Student student, Model model) {
System.out.println("student.getFirstName(): " + student.getFirstName());
System.out.println("student.getLastName(): " + student.getLastName());
return "student-confirmation";
}
#RequestMapping("/processFormV2")
public String processStudentInput(Model model) {
Student student = (Student) model.getAttribute("student");
System.out.println("student.getFirstName(): " + student.getFirstName());
System.out.println("student.getLastName(): " + student.getLastName());
return "student-confirmation";
}
}
and this is my view, student-form.jsp:
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Student Form</title>
</head>
<body>
<form:form action="processFormV2" modelAttribute="student">
First name: <form:input path="firstName"/>
<br>
Last name: <form:input path="lastName"/>
<input type = "submit" value="Submit"/>
</form:form>
</body>
</html>
Now, when the form action is processFormV2 and I submit the form, the line below gives me a null for the student:
Student student = (Student) model.getAttribute("student");
However, when I change the form action to processFormV1 and I submit the form, in the controller method I am able to read the provided values for both names of the Student correctly.
Why is this so?
What is the scope of the values in a Model? Request or Session? From what I have, I always thought the scope is session. Is that right?

Related

"Server error" while hitting a URL from controller in SAP Hybris

I am trying to practice SAP Hybris basic flow and while hitting the URL from the controller I am getting a "server error" as the response as shown below.
The controller looks something like this:
#Controller
public class NewCustomerController {
#RequestMapping(value = "/custid", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String getCustomerNameFromCustId(#RequestBody final Model model){
final List<NewCustomerData> nameList = newCustomerFacade.getCustomerNamefromID();
model.addAttribute("nameList",nameList);
return ControllerConstants.Views.Pages.NewCustomer.CustId;
}
}
The JSP page "custid.jsp" looks something like this:
<%# page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%# taglib prefix="" tagdir="" %>
<html>
<head>
<title>Customer ID detail</title>
</head>
<body>
<h1>This page displays the name of the customer based on customer ID</h1>
<h3>The name of the customer is ${nameList}.<h3>
</body>
</html>
In the dao layer, when I evaluate the call to execute the query, I get this error:
Can anyone help me please what am I doing wrong? I am stuck on this since 2 days now.

Thymeleaf dynamic text

I'm using thymeleaf for email templates but text for these templates are from database.
It looks like this:
<html>
<header>
</header>
<body>
Hello <th:block th:text="${dbText}> </th:block>
</body>
</html>
so, property dbText is String from db.
Question: is it possible to somehow store thymeleaf dynamic property in dbText?
I mean, when I fetch dbText it's
"my friend <th:block th:text="${name}"></th:block>"
so when I try to replace name property in thymeleaf it doesn't replace my value instead of name but render this:
"my friend ${name}"
Ideally, you'd want to drop an object into your template via a model, and then access name as a property of that object.
In your controller:
#RequestMapping(value="/your-uri", method = RequestMethod.GET)
public String showName(Model model) {
User someUser = new User("First", "Last");
model.addAttribute("user", someUser);
}
Then in your view:
<p th:text="${user.firstName} + ' ' + ${user.lastName}></p>
This is assuming that your User class has the following:
private String firstName
private String lastName
// getters & setters
This would output:
<p>First Last</p>

Spring MVC: How do I preserve model attributes in spring validation errors

I searched around on Stack Overflow, but could not find the solution to my query. I have a controller function that adds multiple model attributes on a GET request
#RequestMapping(method = RequestMethod.GET, value = "/showdeletesearchqueryform")
public String showDeleteSearchQuery(final Model model) {
if (LOG.isDebugEnabled()) {
LOG.debug("Fetching all the search query results.");
}
ImmutableList<ArtQueryResults> results = this.searchQueriesService
.getSearchQueries(APPNAME);
// Adding model attribute # 1
model.addAttribute("searchResults", results);
if (LOG.isDebugEnabled()) {
LOG.debug("\"searchResults\" model attribute has been intialized from "
+ results);
}
ArtDeleteQueryRequest request = new ArtDeleteQueryRequest();
request.setAppName(APPNAME);
if (LOG.isDebugEnabled()) {
LOG.debug("Model attribute initialized = " + request);
}
// Adding model attribute # 2
model.addAttribute("deletedAttributes", request);
return "deletesearchqueries";
}
My JSP
<div class="column-group">
<form:form method="POST" action="${pageContext.request.contextPath}/arttestresults/showdeletesearchqueryform" modelAttribute="deletedAttributes">
<form:errors path="*" cssClass="alert alert-danger column lg-units-5 units-2" element="div"/>
<form:hidden path="appName" id="appNameId" htmlEscape="true"/>
<div class = "units-1 column lg-units-12">
<!-- Hidden Key for app name. -->
<form:select path="idsToBeDeleted" id="IdsToBeDeletedSelectId">
<c:forEach items="${searchResults}" var="searchResult" varStatus="loop">
<form:option label="${searchResult.searchQuery}" value="${searchResult.id}" />
</c:forEach>
</form:select>
</div>
<div class="units-1 column lg-units-12">
<%-- This is a hack that make sure that form is submitted on a click. Not sure why form is not being submitted. --%>
<button class="button" type="submit" onclick="javascript:$('form').submit();">Delete Selected Queries</button>
</div>
</form:form>
My controller POST function
#RequestMapping(method = RequestMethod.POST, value = "/showdeletesearchqueryform")
public String deleteSearchQueries(
Model model,
#ModelAttribute(value = "deletedAttributes") #Valid final ArtDeleteQueryRequest request,
final BindingResult result) {
if (result.hasErrors()) {
LOG.warn("There are " + result.getErrorCount() + " validation errors.");
return "deletesearchqueries";
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("The ids to be deleted are " + request.getIdsToBeDeleted());
}
this.searchQueriesService.deleteSearchQueriesById(
ImmutableList.copyOf(request.getIdsToBeDeleted()));
return "redirect:/arttestresults/showdeletesearchqueryform";
}
}
If there is a validation failure, the model attribute searchResults is not being picked up when I return a view on error condition? Is there a way to preserve the other defined model attributes as well?
Seems that you need flash attributes which were added in spring 3.1. Please take a look at example/explanation:
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
The get and the post are different requests. What you get in the post request, is only what comes from the form, so only the "deletedAttributes" model attribute and only the fields that are <input> in the JSP.
You need to put again the searchResults model attribute explicitely like you did in get method.
As suggested by M. Deinum, if one or more attribute(s) will be used by all methods in a controller, you can use a #ModelAttribute annotated method to put it (them) in model automatically.
You can also use SessionAttributes model attributes, that is attributes that are stored in session and not in request. But it is hard to have them properly cleaned from session if user do not post the form but go into another part of the application. You have an example of usage ofSessionAttributes` in Spring's Petclinic example.

Spring MVC 3.2 Thymeleaf Ajax Fragments

I'm building application with Spring MVC 3.2 and Thymeleaf templating engine. I'm a beginner in Thymeleaf.
I have everything working, including Thymeleaf but I was wondering if anyone knows of a simple and clear toturial on how to do simple Ajax request to controller and in result rendering only a part of a template (fragment).
My app has everything configured (Spring 3.2, spring-security, thymeleaf, ...) and works as expected. Now I would like to do Ajax request (pretty simple with jQuery but I don't wan't to use is since Thymeleaf in its tutorial, chapter 11: Rendering Template Fragments (link) mentiones it can be done with fragments.
Currently I have in my Controller
#RequestMapping("/dimensionMenuList")
public String showDimensionMenuList(Model model) {
Collection<ArticleDimensionVO> articleDimensions;
try {
articleDimensions = articleService.getArticleDimension(ArticleTypeVO.ARTICLE_TYPE);
} catch (DataAccessException e) {
// TODO: return ERROR
throw new RuntimeException();
}
model.addAttribute("dimensions", articleDimensions);
return "/admin/index :: dimensionMenuList";
}
the part of the view where I would like to replace <ul></ul> menu items:
<ul th:fragment="dimensionMenuList" class="dropdown-menu">
<li th:unless="${#lists.isEmpty(dimensions)}" th:each="dimension : ${dimensions}">
</li>
</ul>
Any clue is greatly appreciated. Especially if I don't have to include any more frameworks. It's already too much for java web app as it is.
Here is an approach I came across in a blog post:
I didn't want to use those frameworks so in this section I'm using jQuery to send an AJAX request to the server, wait for the response and partially update the view (fragment rendering).
The Form
<form>
<span class="subtitle">Guest list form</span>
<div class="listBlock">
<div class="search-block">
<input type="text" id="searchSurname" name="searchSurname"/>
<br />
<label for="searchSurname" th:text="#{search.label}">Search label:</label>
<button id="searchButton" name="searchButton" onclick="retrieveGuests()" type="button"
th:text="#{search.button}">Search button</button>
</div>
<!-- Results block -->
<div id="resultsBlock">
</div>
</div>
</form>
This form contains an input text with a search string (searchSurname) that will be sent to the server. There's also a region (resultsBlock div) which will be updated with the response received from the server.
When the user clicks the button, the retrieveGuests() function will be invoked.
function retrieveGuests() {
var url = '/th-spring-integration/spring/guests';
if ($('#searchSurname').val() != '') {
url = url + '/' + $('#searchSurname').val();
}
$("#resultsBlock").load(url);
}
The jQuery load function makes a request to the server at the specified url and places the returned HTML into the specified element (resultsBlock div).
If the user enters a search string, it will search for all guests with the specified surname. Otherwise, it will return the complete guest list. These two requests will reach the following controller request mappings:
#RequestMapping(value = "/guests/{surname}", method = RequestMethod.GET)
public String showGuestList(Model model, #PathVariable("surname") String surname) {
model.addAttribute("guests", hotelService.getGuestsList(surname));
return "results :: resultsList";
}
#RequestMapping(value = "/guests", method = RequestMethod.GET)
public String showGuestList(Model model) {
model.addAttribute("guests", hotelService.getGuestsList());
return "results :: resultsList";
}
Since Spring is integrated with Thymeleaf, it will now be able to return fragments of HTML. In the above example, the return string "results :: resultsList" is referring to a fragment named resultsList which is located in the results page. Let's take a look at this results page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
</head>
<body>
<div th:fragment="resultsList" th:unless="${#lists.isEmpty(guests)}" id="results-block">
<table>
<thead>
<tr>
<th th:text="#{results.guest.id}">Id</th>
<th th:text="#{results.guest.surname}">Surname</th>
<th th:text="#{results.guest.name}">Name</th>
<th th:text="#{results.guest.country}">Country</th>
</tr>
</thead>
<tbody>
<tr th:each="guest : ${guests}">
<td th:text="${guest.id}">id</td>
<td th:text="${guest.surname}">surname</td>
<td th:text="${guest.name}">name</td>
<td th:text="${guest.country}">country</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
The fragment, which is a table with registered guests, will be inserted in the results block.
Rendering only Thymeleaf fragments also works well with ModelAndView.
Your controller
#RequestMapping(value = "/feeds", method = RequestMethod.GET)
public ModelAndView getFeeds() {
LOGGER.debug("Feeds method called..");
return new ModelAndView("feeds :: resultsList");
}
Your view
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="resultsList" id="results-block">
<div>A test fragment</div>
<div>A test fragment</div>
</div>
</body>
</html>
What's actually rendered
<div id="results-block">
<div>A test fragment</div>
<div>A test fragment
</div>
</div>
As an alternate version to Sohail's great answer, I want to give a version that using javascript can send the whole th:object to the controller, integrating Thymeleaf in your forms, not having to use #PathVariable which becomes messy or not usable at all when you've forms with many fields.
For the form (using an example which returns an object which has an id and a name Strings, and feeds a combobox with a Map that has some of those objects as values) we have:
<form method="post" th:action="#{/yourMapping}" th:object="${object}" id="yourFormId">
<select th:field="*{mapOfObjects}">
<option
th:each="entry: ${mapOfObjects}"
th:value="${entry.value.id}"
th:text="${entry.value.name}" >
</option>
</select>
<p>Name:
<input type="text" th:field="*{name}" />
</p>
</form>
When this form is submited (using a button with type submit for example) the whole document will be replaced. However we can intercept this submit with javascript and do it the ajax-way. To achieve this, we will add an interceptor to our form using a function. First call the function that adds the interceptor right after the form:
<script>formInterceptor("yourFormId");</script>
And the function looks like this (place it in the head of the document or wherever suits your needs):
<script>
function formInterceptor(formName) {
var $form = $("#" + formName);
$form.on('submit', function(e) {
e.preventDefault();
$.ajax({
url : $form.attr('action'),
type : 'post',
data : $form.serialize(),
success : function(response) {
if ($(response).find('.has-error').length) {
$form.replaceWith(response);
}
else{
$("#ajaxLoadedContent").replaceWith(response);
}
}
});
});
};
</script>
Now whenever the form is submited, this function will trigger, and it will:
Prevent the original form submit
Make an ajax call using the url defined in the form's th:action
Serialize the form data. Your controller will be able to recieve this in an object
Replace the part of your html code with the returned fragment
The replaced part should look like this
<div id="ajaxLoadedContent"></div>
And the controller can recieve the th:object in the form, with it's values filled, like this (Replace Object with your object's type and "object" with a proper name):
#PostMapping(value = /yourMapping)
public String ajaxCtrlExample(#ModelAttribute("object") Object object, Model model) {
return yourFragmentPath;
}
And that's everything. Call the function that adds the interceptor after every form you need in ajax-version.

request.getParameter() returns null

Got a homework assignment that is giving me problems.... Its modifying a JSF project with two pages and a bean to fit MVC2 by adding two more pages and a controller servlet and another bean for the two additional pages. the new main page forwards to either the second new page or the old first page. My issue is response.getParameter() always results in null.
<%#page session="false" import="java.util.Iterator"%>
<%#taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%#taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<jsp:useBean id="status" scope="request" class="JSFRegistration.Status" />
<!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"/>
<title>JSP Page</title>
</head>
<body>
<% if (status!=null && !status.isSuccessful()){%>
<font color="red">Processing errors:
<ul><%Iterator errors=status.getExceptions();
while (errors.hasNext()){
Exception e = (Exception) errors.next();%>
<li><%= e.getMessage()%><%}%></ul></font><%}%>
<form action="LoginServlet" method="POST">
<% String username = request.getParameter("username");
if (username==null) username="";%>
<input type="text" name="usernameTF" value="<%=username%>" />
<% String password = request.getParameter("password");
if (password==null) password="";%>
<input type="password" name="passwordTF" value="<%=password%>" />
<input type="submit" value="Login" />
</form>
</body>
</html>
this is basically a direct copy from our book but the fields I need for the new main page. Same for the controller servlet, a direct copy except only contains the fields I need.
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestDispatcher view = null;
Status status = new Status();
request.setAttribute("status", status);
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username==null || username.length()==0)
status.addException(new Exception("Please enter username"));
if (password==null)
status.addException(new Exception("Please enter password"));
if (!status.isSuccessful()){
view = request.getRequestDispatcher("Login.jsp");
//view.forward(request, response);
}
else
try{
request.setAttribute("username", username);
request.setAttribute("password", password);
view = request.getRequestDispatcher("Registration.jsp");
} catch (Exception e) {
status.addException(new Exception("Error"));
view = request.getRequestDispatcher("Login.jsp");
}
view.forward(request, response);
}
and the Status class, again a direct copy from the book.
public class Status {
private ArrayList exceptions;
public Status(){
exceptions = new ArrayList();
}
public void addException(Exception exception) {
exceptions.add(exception);
}
public boolean isSuccessful(){
return (exceptions.isEmpty());
}
public Iterator getExceptions(){
return exceptions.iterator();
}
regardless of what is typed into the two boxes, stepping through a debug shows the values not getting passed to the parameters. I get the created exceptions printed above the screen for both fields if both have text, if only one has text and when both are empty.
Your request parameter names do not match the input field names. You've assigned the input fields a name of usernameTF and passwordTF. They are then available by exactly those names as request parameter, but you're attempting to get them using the names username and password. So you need either to fix the input field names, or the request parameter names so that they match each other.
By the way, why falling back from a modern MVC framework like JSF to awkward 90's style JSP with mingled business code? Is that really what the homework assignment is asking you? Also the HTML <font> element is deprecated since 1998. Where did you learn about it? Is the quality of the course really good?

Resources