Thymeleaf fragment is loaded before method is executed - java

I have a Controller like this:
#Controller
public class DeviceController {
#Inject
private DeviceService deviceService;
#ModelAttribute("devices")
public List<Device> getDevices() {
return deviceService.getAll();
}
#GetMapping({"/", "index.html"})
public String showIndex() {
return "index";
}
#DeleteMapping(value = "/devices/{id}")
public String deleteOne(#PathVariable("id") long id) {
deviceService.deleteOne(id);
return "index :: devices";
}
}
And a Thymeleaf template like this:
<table id="tbl_device" th:fragment="devices">
<tr> <!-- header --> </tr>
<tr th:each="e : ${devices}" th:object="${d}" th:id="'device_' + *{id}" th:fragment="'device_' + *{id}">
<!-- columns -->
</tr>
</table>
When I call the /devices/{id} DELETE endpoint, I would expect it to return the table without the deleted device. But it actually returns the table including the deleted device. When I debug the code, I can see that getDevices() is called before deleteOne(id).
When I manually reload the page after deleting, the row is (correctly) not displayed anymore.
Why is that? And (how) can I change this behaviour?
Thanks

Why is that?
I recommend to read this article. According to that:
In general, Spring-MVC will always make a call first to that method,
before it calls any request handler methods. That is, #ModelAttribute
methods are invoked before the controller methods annotated with
#RequestMapping are invoked. The logic behind the sequence is that,
the model object has to be created before any processing starts inside
the controller methods.
I doubt you can alter invocation order, but what you can do is additionally pass model attribute to your deleteOne method and modify it there.
#DeleteMapping(value = "/devices/{id}")
public String deleteOne(#PathVariable("id") long id, #ModelAttribute("devices") List<Device> devices) {
deviceService.deleteOne(id);
devices.remove( /* just deleted device */);
return "index :: devices";
}

Related

How to pass object variables for Hibernate criteria?

I'm working in the Spring Framework. How do I pass object variable values into the Hibernate criteria restriction?
This is how I've written them:
#Repository
public class EventSearchDAOImpl implements EventSearchDAO<Event> {
#Autowired
private SessionFactory sessionFactory;
private Session session;
private Transaction trans;
#Override
public List<Event> getAll(Event event) {
session = sessionFactory.openSession();
System.out.println(event.getType()+event.getName());
return session.createCriteria(Event.class).add(Restrictions
.and(Restrictions.like("type", "%"+event.getType()+"%"),
Restrictions.like("name", "%"+event.getName()+"%")))
.list();
}
}
I printed the values in my output console to check via System.out.println(event.getType()+event.getName()); and they do seem to get passed successfully.
This is how I'm passing them via my DefaultController.java class:
#RequestMapping(value = "/searchEvent", method = RequestMethod.POST)
public String searchEvent(Event event, Model model) {
Event e = new Event();
e.setType(event.getType());
e.setName(event.getName());
model.addAttribute("check", eventSearchDAO.getAll(e));
return "redirect:/";
}
A form in my index.jsp page posts data into the above line of code.
To check if the code works, I added the following bit in my index.jsp:
<c:forEach var="i" items="${check}">
${i.name} || ${i.type} <br>
</c:forEach>
If I manually write the restrictions as
return session.createCriteria(Event.class).add(Restrictions
.and(Restrictions.like("type", "%fair%"),
Restrictions.like("name", "%auto%")))
.list();
and make a few tweaks in the controller accordingly, it works just fine. But when I do it by passing the object's variables, it doesn't seem to work. That's my problem. Please help me fix this.
Turns out there was a mistake in my Controller class DefaultController.java, I should've wrote:
return "index";
instead of:
return "redirect:/";
Yes, I'm an idiot.

get variables from jsf query sql db display results on another jsf page [duplicate]

I have started learning JSF, but sadly most tutorials out there present only a log in or a register section.
Can you point me to some more in depth examples? One thing I'm interested in is a page presenting a list of products. I'm on page home and I press on page products so that I can see the latest products added. And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?
One way to solve this would be to create a session scoped managed bean in which I would place different entities updated through other managed beans. I found this kind of approach in some tutorials, but it seems quite difficult and clumsy.
Which would be the best approach to solve a thing like this? What is the correct usage of session scope in two-page master-detail user interface?
What is the correct usage of session scope
Use it for session scoped data only, nothing else. For example, the logged-in user, its settings, the chosen language, etcetera.
See also:
How to choose the right bean scope?
And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?
Typically you use the request or view scope for it. Loading of the list should happen in a #PostConstruct method. If the page doesn't contain any <h:form>, then the request scope is fine. A view scoped bean would behave like a request scoped when there's no <h:form> anyway.
All "view product" and "edit product" links/buttons which just retrieve information (i.e. idempotent) whould be just plain GET <h:link> / <h:button> wherein you pass the entity identifier as a request parameter by <f:param>.
All "delete product" and "save product" links/buttons which will manipulate information (i.e. non-idempotent) should perform POST by <h:commandLink>/<h:commandButton> (you don't want them to be bookmarkable/searchbot-indexable!). This in turn requires a <h:form>. In order to preserve the data for validations and ajax requests (so that you don't need to reload/preinitialize the entity on every request), the bean should preferably be view scoped.
Note that you should basically have a separate bean for each view and also note that those beans doesn't necessarily need to reference each other.
So, given this "product" entity:
#Entity
public class Product {
#Id
private Long id;
private String name;
private String description;
// ...
}
And this "product service" EJB:
#Stateless
public class ProductService {
#PersistenceContext
private EntityManager em;
public Product find(Long id) {
return em.find(Product.class, id);
}
public List<Product> list() {
return em.createQuery("SELECT p FROM Product p", Product.class).getResultList();
}
public void create(Product product) {
em.persist(product);
}
public void update(Product product) {
em.merge(product);
}
public void delete(Product product) {
em.remove(em.contains(product) ? product : em.merge(product));
}
// ...
}
You can have this "view products" on /products.xhtml:
<h:dataTable value="#{viewProducts.products}" var="product">
<h:column>#{product.id}</h:column>
<h:column>#{product.name}</h:column>
<h:column>#{product.description}</h:column>
<h:column>
<h:link value="Edit" outcome="/products/edit">
<f:param name="id" value="#{product.id}" />
</h:link>
</h:column>
</h:dataTable>
#Named
#RequestScoped
public class ViewProducts {
private List<Product> products; // +getter
#EJB
private ProductService productService;
#PostConstruct
public void init() {
products = productService.list();
}
// ...
}
And you can have this "edit product" on /products/edit.xhtml:
<f:metadata>
<f:viewParam name="id" value="#{editProduct.product}"
converter="#{productConverter}" converterMessage="Unknown product, please use a link from within the system."
required="true" requiredMessage="Bad request, please use a link from within the system."
/>
</f:metadata>
<h:messages />
<h:form rendered="#{not empty editProduct.product}>
<h:inputText value="#{editProduct.product.name}" />
<h:inputTextarea value="#{editProduct.product.description}" />
...
<h:commandButton value="save" action="#{editProduct.save}" />
</h:form>
#Named
#ViewScoped
public class EditProduct {
private Product product; // +getter +setter
#EJB
private ProductService productService;
public String save() {
productService.update(product);
return "/products?faces-redirect=true";
}
// ...
}
And this converter for <f:viewParam> of "edit product":
#Named
#RequestScoped
public class ProductConverter implements Converter {
#EJB
private ProductService productService;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
Long id = Long.valueOf(value);
return productService.find(id);
} catch (NumberFormatException e) {
throw new ConverterException("The value is not a valid Product ID: " + value, e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (value instanceof Product) {
Long id = ((Product) value).getId();
return (id != null) ? String.valueOf(id) : null;
} else {
throw new ConverterException("The value is not a valid Product instance: " + value);
}
}
}
You can even use a generic converter, this is explained in Implement converters for entities with Java Generics.
See also:
How to navigate in JSF? How to make URL reflect current page (and not previous one)
JSF Controller, Service and DAO
JSF Service Layer
How to inject #EJB, #PersistenceContext, #Inject, #Autowired, etc in #FacesConverter?
Communication in JSF 2.0 - Contains several examples/hints
As a small improvement to what BalusC recommended, sometimes you can remove the required / requiredMessage part from the <f:viewParam> of your "details" screen and instead use the conditional rendering of the editing form (as BalusC did) with a reverse condition for recommending a specific link for the "list/master" screen or, even use a viewAction that would test the param and force a redirect to that list.

spring boot / thymeleaf nested loop object access

I am using Spring Boot and Thymeleaf to create a single landing page for
my application. For this, I need to render a List of Host objects that all
contain a Container.
Here is the relevant code:
public class Container {
private String name;
private String baseUrl;
private String status;
public Container(String name, String baseUrl, String status) {
this.name = name;
this.baseUrl = baseUrl;
this.status = status;
}
public String getName() { return name; }
public String getBaseUrl() { return baseUrl; }
public String getStatus() { return status; }
}
public class Host {
private HashMap<String, Container> containers;
....
public List<Container> getContainers() {
return containers.values();
}
}
#RequestMapping("/")
public class IndexController {
#RequestMapping("/")
public String getIndex(Model model) {
model.addAttribute("hosts", hostRepository.getAllServers());
return "index";
}
}
Now I want to iterate over all servers and display the information about each Container in a table.
My Thymeleaf template looks like this:
<div class="panel panel-default" th:each="host : ${hosts}">
<div class="panel-heading">
<b th:text="${host.name}">Host X</b>
<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>URL</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr th:each="container : ${host.getContainers()}">
<!-- HERE IS THE PROBLEM -->
<td th:text="${container.name}">Service1</td>
<td th:text="${container.baseUrl}">domain.com/api/url</td>
<td th:text="${container.status}">RUNNING</td>
<!-- HERE ENDS THE PROBLEM -->
</tr>
</tbody>
</table>
</div>
</div>
</div>
My problem is the part where is access the container's properties (marked by the commentary).
Every time I get a SpringEL Exception. If I remove the th:text="${container.xy}" and replaces it with th:text="${container} a String version of the container is shown so I have access to the object and the loop it working properly. I also tried to replace the field access with getters (e.g. getStatus()) but it also does not work.
Thanks for your help. If you need more information, feel free to ask.
Setup:
Java 8
Spring Boot Starter Web
Thymeleaf
edit: The exception thrown is: nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "container.name" (index:35) where index:35 is the first problematic line.
The toString() output when using ${container} is jenkins=com.my.app.Container#7552c269 and jenkins is the name attribute of the Container instance.
Solution It seemed that the nested loop was iterating over a Map instead of a List. Changing ${container.xy} to ${container.getValue().xy} solved the problem.
Solution
It seemes that the nested loop was iterating over a org.thymeleaf.util.EvaluationUtil$MapEntry instead of a List. Changing ${container.xy} to ${container.getValue().xy} solved the problem.
Bits learned along the way:
Override the toString() method to obtain formatted information about the object iterating over. In this case the output was key=value which altough value was expected. This gave a hint that the current object must be something else than a Container instance
Look at the stack trace of Thymeleaf (usually its a hint that something is null or not public)
Use getClass() on the current object during the iteration to check if something went wrong here

Struts 1.3 ActionForm - retrieving a collection from multiselect dropdown insted of using arrary

What I cannot figure out is how to select multiple input from multielect drop down to my Action.By using a collections instead of array.I have posted the both jsp code and actionform.
This is my jsp:
<table class="table-striped" style="width: 100%;">
<tr>
<th style="border: none;"><br><br><br><br><label class="control-label" >Grade</label></th>
<th style="border: none;">
<html:select name="GradeBoardConfigureForm" property="grade" multiple="">
<html:option value="">Grade List</html:option>
<html:optionsCollection name="GradeBoardConfigureForm" property="gradelist" label="grade" value="gradeid"/>
</html:select>
</th>
</tr>
</table>
FormBean:
public class GradeBoardConfigureForm extends ActionForm {
private String board;
private List grade;
private List gradelist;
private List boardlist;
public String getBoard() {
return board;
}
public void setBoard(String board) {
this.board = board;
}
public List getGradelist() {
gradelist = new ArrayList<>();
DAOFactory factory = HibernateDAOUtil.getDAOFactory();
GradeDao gradedao = factory.getGradeDao();
List<Academicgradeform> gradedaolist = gradedao.list();
gradelist.addAll(gradedaolist);
return gradelist;
}
public void setGradelist(List gradelist) {
this.gradelist = gradelist;
}
public List getBoardlist() {
boardlist = new ArrayList<>();
DAOFactory factory = HibernateDAOUtil.getDAOFactory();
BoardDao boarddao = factory.getBoardDAO();
List<Academicboardform> boarddaolist = boarddao.list();
boardlist.addAll(boarddaolist);
return boardlist;
}
public void setBoardlist(List boardlist) {
this.boardlist = boardlist;
}
public List getGrade() {
return grade;
}
public void setGrade(List grade) {
this.grade = grade;
}
}
As far as I know it is not directly possible and is not Struts1 philosophy.
A form bean should only be a piece of code that helps to transfer data between the controller (a singleton, so it is stateless) and a view. It is not intended to be a domain object.
So your GradeBoardConfigureForm is a good example of you should not be done. You mix DAO in a form bean. It will be hard to write and hard to test and will lead to poorly structured code. And your problem lays here. If you had a domain object distinct from your form bean, the domain object would have a List, and the DAO layer whould bind the list to the database. And the controller (Action in stuts1) would copy the list to an Array in the form bean, pass it to the view, and in submit phase will use a populated Array according to your needs.
But I have 3 strong advices for you :
Struts1 is deprecated (according to official Apache Struts page), do not use it except for maintaining existing code - Struts2 or Spring MVC are current alternatives
if you are a beginner, start by following existing tutorials
before coding, carefully design your program structure. If you do not understand what I mean here, read (again if you already had a look at it) this document linked at the bottom of Apache strus1 page : Understanding JavaServer Pages Model 2 architecture.

Does Spring MVC form submit data bind children objects automagically?

I have a data model that is something like this:
public class Report {
// report owner
private User user;
... typical getter setter ...
}
public class User {
... omitted for clarity
}
What happens is when a report is created, the current user is set to the report user object. When the report is edited, the spring controller handling the POST request is receiving a report where the user object is null. Here is what my controller looks like:
#Controller
#RequestMapping("/report")
public class ReportController {
#RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public String editReport(#PathVariable Long id, Model model) {
Report r = backend.getReport(id); // fully loads object
model.addAttribute("report", report);
return "report/edit";
}
#RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
public String process(#ModelAttribute("report") Report r) {
backend.save(r);
return "redirect:/report/show" + r.getId();
}
}
I ran things throw the debugger and it looks like in the editReport method the model object is storing the fully loaded report object (I can see the user inside the report). On the form jsp I can do the following:
${report.user.username}
and the correct result is rendered. However, when I look at the debugger in the process method, the passed in Report r has a null user. I don't need to do any special data binding to ensure that information is retained do I?
It seems that unless the object being edited is stored in the #SessionAttributes, then spring will instantiate a new object from the information included in the form. Tagging the controller with #SessionAttributes("report") resolved my issue. Not sure of the potential impact of doing so however.

Categories

Resources