#PathVariable annotated variable contains wrong value in conjunction with #ControllerAdvice & StringEditor (PropertyEditor) - SpringMVC - spring-mvc

When using a #ControllerAdvice and having a MVC-Controller with a GET-Mapping that has a path variable, this path variable is sometimes assigned a wrong value when used in concurrent requests.
The original function of the controller method in question was to receive an id for which an image was fetched and returned as a ResponseEntity<byte[]>. But the supplied id via the path variable was sometimes wrong, resulting in a wrongly served image. I could narrow it down to the usage of a controller advice that registered a custom editor (String) via data binding.
When removing the data binding of the property editor from the controller advice, everything was hunky dory again.
But why is this happening and how can it be avoided? Is a controller advice the wrong approach here? Should the string editor be bound in every controller rather than using a controller advice?
The following code parts can be used to reproduce the error:
Thymeleaf-Template that triggers the result:
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#locale.language}">
<head>
<meta charset="UTF-8">
<title>example</title>
<div th:each="exampleCounter : ${#numbers.sequence(0, 99999)}">
<img th:src="#{/example/{number}(number = ${exampleCounter})}"/>
</div>
</head>
<body>
</body>
</html>
String editor in question (whole class):
package org.example.support;
import org.springframework.stereotype.Component;
import java.beans.PropertyEditorSupport;
#Component
public class StringEscapeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
// do something here, not important for result in question
setValue(text);
}
}
Controller advice that binds the string editor (relevant fragment):
#ControllerAdvice
public class ExampleControllerAdvice {
private final StringEscapeEditor stringEscapeEditor;
#Autowired
public ExampleControllerAdvice(StringEscapeEditor stringEscapeEditor) {
this.stringEscapeEditor = stringEscapeEditor;
}
#InitBinder
public void dataBinding(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(String.class, stringEscapeEditor);
}
}
Controller with #GetMapping/#PathVariable (relevant fragment):
#Controller
public class ExampleController {
#GetMapping(path = "/example/{xyz}")
public ResponseEntity<byte[]> getImageAsResponseEntity(#PathVariable("xyz") String xyz, HttpServletRequest request) {
if (!request.getRequestURI().contains(xyz)) {
System.out.println("URI: " +request.getRequestURI() + " | PathVariable: "+ xyz);
}
// ... return some imagebytes ...
}
}
When requesting the url that serves above thymeleaf-template you'll see that the #PathVariable-Variable xyz sometimes is different from the path-segment in the uri that was requested. The path variable (xyz) is wrong. The path-segment received via request is correct.
Thus the code fragment is triggered:
if (!request.getRequestURI().contains(xyz)) {
System.out.println("URI: " +request.getRequestURI() + " | PathVariable: "+ xyz);
}
URI: /example/62 | PathVariable: 61
URI: /example/76 | PathVariable: 77
URI: /example/80 | PathVariable: 79
URI: /example/133 | PathVariable: 134
URI: /example/207 | PathVariable: 208
URI: /example/333 | PathVariable: 323
--- Solution ---
The Binding of the Custom Property Editor (StringEscapeEditor) was wrong. You have to bind it as a new instance rather than injecting it. This is how the binding within the controller advice should look like instead:
#ControllerAdvice
public class ExampleControllerAdvice {
#InitBinder
public void dataBinding(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(String.class, new StringEscapeEditor());
}
}

Related

Spring thymeleaf Form url cannot be resolved

I want to know how to transfer parameters in spring form mvc platform.
First, Below code is spring form java file.
public class PostForm {
#NotNull
#Size(max=30, message="type id within 30 limits")
private String title;
#NotNull
#Size(max=100, message="type id within 100 limits")
private String Content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
And the next file is the bounded edit.html file
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<title>Blog modification</title>
</head>
<body>
<h1>Please, Modifiy.</h1>
<form method="post" th:object="${postForm}">
<div><label for="title">title</label></div>
<input id="title" type="text" name="title" th:value="*{title}" />
<span class="formError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Input title is wrong</span>
<div><label for="content" th:value="*{title}">Content</label></div>
<textarea name="content" rows="20" width="200" th:value="*{content}"></textarea>
<span class="formError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Input content is wrong</span>
<div>
<input type="submit" value="Modify" />
Cancel
</div>
</form>
</body>
</html>
Input link url to form is like below,
<td>
edit<br/>
delete
</td>
But the exception is thrown in the spring mvc controller codes.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
#RequestMapping(value="/posts/edit/{id}", method = RequestMethod.POST)
public String edit(#PathVariable("id") Long id, #Valid PostForm postForm, BindingResult bindingResult) {
Post p = postService.findById(id);
postForm.setTitle(p.getTitle());
postForm.setContent(p.getBody());
.....
The exception is
ERROR 4024 --- [nio-8080-exec-4] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-4] Exception processing template "posts/edit/{id}": Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:870) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
I have no idea how to transfer parameter in Spring Thymeleaf form template.
In Sprig MVC, when #ReqeustMapping annotation with GET Method is called, it tries to find the html template with the name defined in the return value.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
So here you must return the name of the html template in the resources folder (not the url)
So I guess it's supposed to be
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "views/mytemplate";
}
The error obviously indicates that it can't find the template under the resources folder. What your code does is try to locate the thymeleaf template in the 'edit' folder under 'posts' folder under the resources folder with the name of '{id}' but that's not there so it throws the error.
My suggestion is to change the return value of the GET method as I mentioned above.
If you need to pass any parameters to the view, use Model class.
If the parameters' value must be calculated from the {id} then you can use #PathVariable to map the id to a parameter.
#RequestMapping("/posts/edit/{id}")
public String edit(#PathVariable(value="id") String id, Model model) {
// do something here to get values using the id
....
model.addAttribute("parameter1", parameter1);
return "views/mytemplate";
}
By the way you don't need PostForm parameter in the GET method since it does not pass any postForm parameters in the body when it's called. You can leave it blank.
Hope this helps, have fun coding! :)

I have retrieved information from a database, but how do I format it properly? Perhaps using html [duplicate]

I'm implementing MVC using JSP and JDBC. I have imported a database class file to my JSP file and I would like to show the data of a DB table. I don't know how I should return the ResultSet from the Java class to the JSP page and embed it in HTML.
How can I achieve this?
In a well designed MVC approach, the JSP file should not contain any line of Java code and the servlet class should not contain any line of JDBC code.
Assuming that you want to show a list of products in a webshop, the following code needs to be created.
A Product class representing a real world entity of a product, it should be just a Javabean.
public class Product {
private Long id;
private String name;
private String description;
private BigDecimal price;
// Add/generate getters/setters/c'tors/equals/hashcode boilerplate.
}
A DAO class which does all the nasty JDBC work and returns a nice List<Product>.
public class ProductDAO {
private DataSource dataSource;
public ProductDAO(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<Product> list() throws SQLException {
List<Product> products = new ArrayList<Product>();
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT id, name, description, price FROM product");
ResultSet resultSet = statement.executeQuery();
) {
while (resultSet.next()) {
Product product = new Product();
product.setId(resultSet.getLong("id"));
product.setName(resultSet.getString("name"));
product.setDescription(resultSet.getString("description"));
product.setPrice(resultSet.getBigDecimal("price"));
products.add(product);
}
}
return products;
}
}
A servlet class which obtains the list and puts it in the request scope.
#WebServlet("/products")
public class ProductsServlet extends HttpServlet {
#Resource(name="jdbc/YourDB") // For Tomcat, define as <Resource> in context.xml and declare as <resource-ref> in web.xml.
private DataSource dataSource;
private ProductDAO productDAO;
#Override
public void init() {
productDAO = new ProductDAO(dataSource);
}
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<Product> products = productDAO.list();
request.setAttribute("products", products); // Will be available as ${products} in JSP
request.getRequestDispatcher("/WEB-INF/products.jsp").forward(request, response);
} catch (SQLException e) {
throw new ServletException("Cannot obtain products from DB", e);
}
}
}
Finally a JSP file in /WEB-INF/products.jsp which uses JSTL <c:forEach> to iterate over List<Product> which is made available in EL by ${products}, and uses JSTL <c:out> to escape string properties in order to avoid XSS holes when it concerns user-controlled input.
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/format" prefix="fmt" %>
...
<table>
<c:forEach items="${products}" var="product">
<tr>
<td>${product.id}</td>
<td><c:out value="${product.name}" /></td>
<td><c:out value="${product.description}" /></td>
<td><fmt:formatNumber value="${product.price}" type="currency" currencyCode="USD" /></td>
</tr>
</c:forEach>
</table>
To get it to work, just call the servlet by its URL. Provided that the servlet is annotated #WebServlet("/products") or mapped in web.xml with <url-pattern>/products</url-pattern>, then you can call it by http://example.com/contextname/products
See also:
How to avoid Java code in JSP files?
doGet and doPost in Servlets
How should I connect to JDBC database / datasource in a servlet based application?
Design Patterns web based applications
RequestDispatcher.forward() vs HttpServletResponse.sendRedirect()
How to map a ResultSet with unknown amount of columns to a List and display it in a HTML table?
How do I pass current item to Java method by clicking a hyperlink or button in JSP page?
MVC, in a web application context, doesn't consist in using a class from a JSP. It consists in using the following model :
browser sends a request to a web server
the web server is configured so that the request is handled by a servlet or a filter (the controller : Java code, not JSP code)
The servlet/filter usually dispatches the request to a specific class (called an Action, the specific part of the controller), based on configuration/annotations
The action executes the business logic (i.e. fetch the data from the database in your example : the model)
The action forwards the request to a JSP. The role of the JSP is only to generate HTML code (i.e. display your data : the view)
Since the JSP usually uses JSP tags (the JSTL, for example) and the JSP expression language, and since JSP tags and the EL are designed to get information from JavaBeans, you'd better have your data available in the form of JavaBeans or collections of JavaBeans.
The role of the controller (the action class) is thus to fetch the data, to create JavaBean instances containing the data, in a suitable format for the JSP, to put them in request attributes, and then to dispatch to the JSP. The JSP will then iterate through the JavaBean instances and display what they contain.
You should not implement the MVC framework yourself. Use existing ones (Stripes, Struts, etc.)
I don't know how should I return the ResultSet from the class file to the JSP page
Well, you don't.
The point of MVC is to separate your model ( the M DB info in this case ) from your view ( V a jsp, in this case ) in such a way you can change the view without braking to application.
To do this you might use an intermediate object to represent your data ( usually called DTO - after Data Transfer Object -, don't know how they call it these days ), and other object to fetch it ( usually a DAO ).
So basically you have your JSP file, get the request parameters, and then invoke a method from the DAO. The dao, internally has the means to connect to the db and fetch the data and builds a collections of DTO's which are returned to the JSP for rendering.
Something like this extremely simplified ( and insecure ) code:
Employee.java
class Employee {
String name;
int emplid;
}
EmployeeDAO.java
class EmployeeDAO {
... method to connect
etc.
List<Employee> getAllNamed( String name ) {
String query = "SELECT name, emplid FROM employee where name like ?";
ResultSet rs = preparedStatement.executeQuery etc etc.
List<Employee> results = ....
while( rs.hasNext() ) {
results.add( new Employee( rs.getString("name"), rs.getInt("emplid")));
}
// close resources etc
return results;
}
}
employee.jsp
<%
request.setAttribute("employees", dao.getAllNamed( request.getParameter("name") );
%>
<table>
<c:forEach items="${employees}" var="employee">
<tr><td>${employee.emplid}</td><td>${employee.name}</td></tr>
</c:forEach>
</table>
I hope this give you a better idea.
I have a problem. I don't understand clearly the code. I have a similar problem with my code.
I have created database SQL and filled up. Then I want to implement a MainServlet (code below) that richieve data from database and in a different jsp page, I want to insert that data in section like h1, h2 ecc... I must use the ${} sintax but I don't know how do that.
Briefly, In jsp file (code below, I MUST USE ${} SINTAX) I want to "call" MainServlet and there I want to richieve data from database and view in jsp file.
I hope I have explained correctly, thank you very much!
MainServlet.java
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class MainServlet
*/
#WebServlet({ "/MainServlet" })
public class MainServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String PATH_JSP = "/WEB-INF/";
/**
* #see HttpServlet#HttpServlet()
*/
public MainServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* #see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
}
/**
* #see Servlet#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String doveAndare = request.getParameter("azione");
if(doveAndare==null)
doveAndare = "index";
try {
String driverString = "com.mysql.cj.jdbc.Driver";
Class.forName(driverString);
String connString = "jdbc:mysql://localhost:3306/ldd_jewels?user=root&password=";
Connection conn = DriverManager.getConnection(connString);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM JEWEL");
while (rs.next() == true) {
System.out.println(rs.getString("Category") + "\t" + rs.getString("Name"));
/* I try that but does not work
request.setAttribute("name", rs.getString("Name"));
javax.servlet.RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/widering_male.jsp");
dispatcher.forward(request, response); */
}
stmt.close();
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
request.getRequestDispatcher(PATH_JSP+doveAndare+".jsp").forward(request, response);
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
doublerow.jsp
<section id="portfolio-details" class="portfolio-details">
<div class="container">
<div class="row gy-4">
<div class="col-lg-8">
<div class="portfolio-details-slider swiper">
<div class="swiper-wrapper align-items-center">
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_1.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_2.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_3.jpg" alt="" />
</div>
</div>
<div class="swiper-pagination"></div>
</div>
</div>
<div class="col-lg-4">
<div class="portfolio-info">
<h3>Product details</h3>
<ul>
<li><strong>Code</strong>: 1S3D5</li>
<li><strong>Category</strong>: Bracelets</li>
<li><strong>Name</strong>: Double Row Hinged Bangle</li>
<li><strong>Gender</strong>: Female</li>
<li><strong>Material</strong>: Yellow gold</li>
<li><strong>Size</strong>: 121mm</li>
<li><strong>Price</strong>: €5500</li>
</ul>
</div>
<div class="portfolio-description">
<h2>Description of product</h2>
<p>
The entwined ends of Tiffany Knot’s signature motif symbolize
the power of connections between people. Balancing strength
and elegance, each Tiffany Knot design is a complex feat of
craftsmanship. This bangle is crafted with yellow gold and
polished by hand for high shine. Wear on its own or partnered
with classic silhouettes for an unexpected pairing.
</p>
</div>
</div>
</div>
</div>
</section>
This is my database:
I want to insert each jewel in different pages (each jewel have a jsp file)
You can use the <c:forEach > tag
you can find a detailed example in the following link example use
I think it will be better for you to contain the data of the table into a collection such as list and return the list from the Java class and reuse this collection in the JSP.

In JavaFX 8 can I provide a stylesheet from a String?

Is it possible to wrap a whole Stylesheet in a string and apply it to a certain node?
Usage case would be to add specific (non changeble) behavior for PseudoClass.
I know I can use pane.getStylesheets().add(getClass().getResource("mycss.css").toExternalForm());, but I would like to know if there's some way to embrd it direcly in source; something along the lines:
pane.getStylesheets().add(
".button:ok { -fx-background-color: green; }\n"+
".button:ko { -fx-background-color: red; }");
I found a way of doing this by defining a new URL connection:
private String css;
public void initialize() {
...
// to be done only once.
URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
...
}
private void updateCss(Node node) {
// can be done multiple times.
css = createCSS();
node.getStylesheets().setAll("internal:"+System.nanoTime()+"stylesheet.css");
}
private class StringURLConnection extends URLConnection {
public StringURLConnection(URL url){
super(url);
}
#Override public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(css);
}
}
private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {
URLStreamHandler streamHandler = new URLStreamHandler(){
#Override protected URLConnection openConnection(URL url) throws IOException {
if (url.toString().toLowerCase().endsWith(".css")) {
return new StringURLConnection(url);
}
throw new FileNotFoundException();
}
};
#Override public URLStreamHandler createURLStreamHandler(String protocol) {
if ("internal".equals(protocol)) {
return streamHandler;
}
return null;
}
}
Obviously protocol "internal" can be any (non clashing) well-formed string and (in this simple example) filepath is compeltely ignored.
I use this to set the global .css, so I do not need to remember multiple strings.
It seems the Stream is opened just once, but I do not know if this holds true in all cases.
Feel free to complicate the code as needed ;)
Credit for this method goes to Jasper Potts (see this example)
Here is my CSS updater class based on ZioBytre's answer (+1 works very well).
This is a self contained class that can easily be copied to a project and used as it is.
It has a dependency on the commons IO IOUtils class to return a Stream based on a String. But this could easily be inlined or replaced by another library if needed.
I use this class in a project where the CSS is dynamically editable inside the application, on the server side, and pushed to the JavaFX clients. It could be used in any scenario where the CSS string does not come from a file or URL but from another source (server app, database, user input...)
It has a method to bind a string property so that the CSS changes will be automatically applied as soon as they happen.
/**
* Class that handles the update of the CSS on the scene or any parent.
*
* Since in JavaFX, stylesheets can only be loaded from files or URLs, it implements a handler to create a magic "internal:stylesheet.css" url for our css string
* see : https://github.com/fxexperience/code/blob/master/FXExperienceTools/src/com/fxexperience/tools/caspianstyler/CaspianStylerMainFrame.java
* and : http://stackoverflow.com/questions/24704515/in-javafx-8-can-i-provide-a-stylesheet-from-a-string
*/
public class FXCSSUpdater {
// URL Handler to create magic "internal:stylesheet.css" url for our css string
{
URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
}
private String css;
private Scene scene;
public FXCSSUpdater(Scene scene) {
this.scene = scene;
}
public void bindCss(StringProperty cssProperty){
cssProperty.addListener(e -> {
this.css = cssProperty.get();
Platform.runLater(()->{
scene.getStylesheets().clear();
scene.getStylesheets().add("internal:stylesheet.css");
});
});
}
public void applyCssToParent(Parent parent){
parent.getStylesheets().clear();
scene.getStylesheets().add("internal:stylesheet.css");
}
/**
* URLConnection implementation that returns the css string property, as a stream, in the getInputStream method.
*/
private class StringURLConnection extends URLConnection {
public StringURLConnection(URL url){
super(url);
}
#Override
public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return IOUtils.toInputStream(css);
}
}
/**
* URL Handler to create magic "internal:stylesheet.css" url for our css string
*/
private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {
URLStreamHandler streamHandler = new URLStreamHandler(){
#Override
protected URLConnection openConnection(URL url) throws IOException {
if (url.toString().toLowerCase().endsWith(".css")) {
return new StringURLConnection(url);
}
throw new FileNotFoundException();
}
};
#Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("internal".equals(protocol)) {
return streamHandler;
}
return null;
}
}
}
Usage :
StringProperty cssProp = new SimpleStringProperty(".root {-fx-background-color : red}");
FXCSSUpdater updater = new FXCSSUpdater(scene);
updater.bindCss(cssProp);
//new style will be applied to the scene automatically
cssProp.set(".root {-fx-background-color : green}");
//manually apply css to another node
cssUpdater.applyCssToParent(((Parent)popover.getSkin().getNode()));
For anyone who is writing framework level code that does not want to use up the one and only override of the global, static url stream factory, you can instead tie into the internal "service loader" framework in the URL class itself.
To do this, you must create a class named Handler extends URLStreamHandler and update the system property java.protocol.handler.pkgs to point to the package of that class, minus the final package suffix. So, com.fu.css would set the property to com.fu, then all css:my/path requests would route to this handler.
I will paste the class I am using below; forgive the weird collections and supplier interfaces; you can guess what these do and replace them with standard utilities without much trouble.
package xapi.jre.ui.css;
import xapi.collect.X_Collect;
import xapi.collect.api.CollectionOptions;
import xapi.collect.api.StringTo;
import xapi.fu.Out1;
import xapi.io.X_IO;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
/**
* I abhor the name of this class,
* but it must be called "Handler" in order for java.net.URL to be able to find us.
*
* It sucks, but it's not our api, and it's the only way to get dynamic stylesheets in JavaFx,
* short of overriding the url stream handler directly (and this can only be done once in a single
* JVM, and as framework-level code, it is unacceptable to prevent clients from choosing to
* override the stream handler themselves).
*
* Created by James X. Nelson (james #wetheinter.net) on 8/21/16.
*/
public class Handler extends URLStreamHandler {
private static final StringTo<Out1<String>> dynamicFiles;
static {
// Ensure that we are registered as a url protocol handler for css:/path css files.
String was = System.getProperty("java.protocol.handler.pkgs", "");
System.setProperty("java.protocol.handler.pkgs", Handler.class.getPackage().getName().replace(".css", "") +
(was.isEmpty() ? "" : "|" + was ));
dynamicFiles = X_Collect.newStringMap(Out1.class,
CollectionOptions.asConcurrent(true)
.mutable(true)
.insertionOrdered(false)
.build());
}
public static void registerStyleSheet(String path, Out1<String> contents) {
dynamicFiles.put(path, contents);
}
#Override
protected URLConnection openConnection(URL u) throws IOException {
final String path = u.getPath();
final Out1<String> file = dynamicFiles.get(path);
return new StringURLConnection(u, file);
}
private static class StringURLConnection extends URLConnection {
private final Out1<String> contents;
public StringURLConnection(URL url, Out1<String> contents){
super(url);
this.contents = contents;
}
#Override
public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return X_IO.toStream(contents.out1(), Charset.defaultCharset().name());
}
}
}
Now, any code can call Handler.registerStylesheet("my/path", ()->"* { -fx-css: blah }");, and you can use this stylesheet anywhere via "css:my/path".
Note that I am only looking at the path portion of the url; I intend to leverage query parameters to further increase the dynamism (by using a css factory that accepts a map of parameters), but that is beyond the scope of this question.
I looked at the documentation and I don’t see a built-in way to do that. getStylesheets is the only stylesheet-related method in Parent, and it only accepts “string URLs linking to the stylesheets”, not stylesheets themselves. It returns a generic ObservableList, so its return value has no special methods for different types; only a generic add. This is consistent with getResource returning a URL, and toExternalForm() merely returning a String version of that URL object.
However, there is one thing you could try: a data URI. Instead of passing in a generated URI to a stylesheet file, pass in a data URI whose contents are that stylesheet. I don’t know if the API would accept that kind of URI, though, given that the CSS Reference Guide linked in getStylesheets’s documentation says
A style sheet URL may be an absolute URL or a relative URL.
Try a really simple data URI first to see if it works. You can generate one using this online tool. If Java does accept a data URI, then you just need to wrap your CSS-containing String with some method call that converts a String to a data URI, something like this:
pane.getStylesheets().add(new DataURI(
".button:ok { -fx-background-color: green; }\n"+
".button:ko { -fx-background-color: red; }").toString());
The class DataURI is hypothetical. If JavaFX accepts a manually-generated data URI, then you will have to find a library that provides that DataURI class yourself; I’m sure one exists somewhere.
There is also a way to specify inline CSS for a certain Node as a String, which is almost what you are looking for. It is mentioned in the CSS Reference Guide:
CSS styles can come from style sheets or inline styles. Style sheets are loaded from the URLs specified in the stylesheets variable of the Scene object. If the scene graph contains a Control, a default user agent style sheet is loaded. Inline styles are specified via the Node setStyle API. Inline styles are analogous to the style="…" attribute of an HTML element.
However, it sounds like it does not support selectors in the CSS, only rules – so rather than saying .red { color: red; }, you would only be able to write color: red;, and it would apply to all children of that Node. This doesn’t sound like what you want. So a data URI is your only hope.
EDIT: While this is a smart idea (I didn't know about data URIs before) it doesn't work. I have the same requirement so I tried. It doesn't raise an exception but there is a warning in the the logs and the styles are not applied :
I used this style :
.root{
-fx-font-family: "Muli";
-fx-font-weight: lighter;
-fx-font-size: 35pt;
-fx-padding: 0;
-fx-spacing: 0;
}
And using the provided tool generated the following data URI :
data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D
Applying it to my scene :
scene.getStylesheets().add("data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D");
Results in (pardon my French, AVERTISSEMENT=WARNING):
janv. 07, 2015 12:02:03 PM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
AVERTISSEMENT: Resource "data:text/css;charset=utf-8,%23header%7B%0D%0A%20%20%20%20-fx-background-color%3A%23002D27%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-text-fill%3A%20%23fff%3B%0D%0A%7D" not found.
So sadly JavaFX seems not to be aware of data URIs.
Since JavaFX 17 it is now possible to use data URIs.
For example,
scene.getStylesheets().add("data:text/css;base64," + Base64.getEncoder().encodeToString("* { -fx-color: red; }".getBytes(StandardCharsets.UTF_8)));
will simply work in JavaFX 17.

Binding comma-separated strings to domain objects using Spring PropertyEditors

I am trying to bind a text field (a comma-separated list of Tag names, Tag being my domain object) to a variable which is a List of Tag objects, in another domain object called Expense. So, a user enters a list of tags for an expense item in the form and this gets bound to a Collection of Tag items in the Expense domain object. So far, what I have done in my code is this:
my jsp file has:
<tr>
<td>Tags</td>
<td><form:input path="tags" type="text" name="tags" id="tags_formfield" /></td>
</tr>
my domain object has:
//bi-directional many-to-many association to Tag
#ManyToMany()
#JoinTable(name="EXPENSES_X_TAGS",
joinColumns= #JoinColumn(name="EXPENSE_ID", referencedColumnName="ID"),
inverseJoinColumns= #JoinColumn(name="TAG_ID", referencedColumnName="ID"))
private List<Tag> tags;
my controller has:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, new TagsEditor());
}
I have also tried:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Tag.class, new TagsEditor());
}
And
my property editor is:
package com.transience.sandbox.converters;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.transience.sandbox.domain.Tag;
import com.transience.sandbox.services.ITagService;
public class TagsEditor extends PropertyEditorSupport {
#Autowired
ITagService tagService;
protected final Log logger = LogFactory.getLog(getClass());
#Override
// Converts a comma separated String of tagNames to a List<Tag> (when submitting form)
public void setAsText(String stringOfTagNames) {
List<Tag> tags = null;
String[] tagNames = stringOfTagNames.split(",");
logger.info("Looping through tagNames now...");
for(String tagName : tagNames) {
logger.info("tag name: " + tagName);
Tag tag = tagService.findByTagName(tagName);
tags.add(tag);
}
logger.info("Trying to setValue now...");
this.setValue(tags);
logger.info("Value of List of Tags set successfully...");
}
}
But I am getting the following exception in form submission:
Error 500--Internal Server Error
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'expense' on field 'tags': rejected value [booze,lunch]; codes [methodInvocation.expense.tags,methodInvocation.tags,methodInvocation.java.util.List,methodInvocation]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [expense.tags,tags]; arguments []; default message [tags]]; default message [Property 'tags' threw exception; nested exception is java.lang.NullPointerException]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:110)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:74)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:155)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:900)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
I am pretty sure I am doing something fundamentally incorrect, but what am I doing wrong? I am aware of the Converters approach, but I am curious to know how this can be done using PropertyEditors
Thanks,
Sanjay
I solved it by adding valueOf(String) to the domain object (also toString is important for backward conversion).
Somehow Spring is already able to convert csv to List.
Found answer here: How to bind a comma separated string to a Java collection (List) in Spring form?

Spring 3 MVC: Issue binding to list form fields on submit

Let me introduce my issue by providing some of the code in question.
First my form object:
public class OrgChartForm {
List<OrgChartFormElement> orgChartFormElements;
public OrgChartForm() {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
}
private OrgChartFormElement createOrgChartFormElementFromMprsStructureYear(MprsStructureYear structureYear){
OrgChartFormElement element = new OrgChartFormElement();
element.set.... // populate element based on attribute values from structureYear param
return element;
}
public void createOrgChartFormElements(List<MprsStructureYear> structureYears) {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
for(MprsStructureYear structureYear:structureYears){
orgChartFormElements.add(createOrgChartFormElementFromMprsStructureYear(structureYear));
}
}
// expected getters and setters
}
The form contains a simple list of OrgChartFormElements
public class OrgChartFormElement {
private boolean selected;
private String elementLabel;
private Long id;
//default constructor, getters and setters
}
I am using context:component-scan and mvc:annotation-driven, so my controller looks like:
#Controller
public class OrganisationStatusController{
#Autowired(required=true)
// dependencies here
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.GET)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId, Model model) throws Exception {
List<MprsStructureYear> orgStructuure = getOrganisationService().getOrganisationStructureForFinyear(finyearId);
OrgChartForm orgChartForm = new OrgChartForm();
orgChartForm.createOrgChartFormElements(orgStructuure);
model.addAttribute("orgChartForm", orgChartForm);
return "finyear/organisationchart/view";
}
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.POST)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId,#ModelAttribute("orgChartForm") OrgChartForm orgChartForm, BindingResult result, Model model) throws Exception {
System.out.println("Found model attribute: " + model.containsAttribute("orgChartForm"));
List<OrgChartFormElement> elements = orgChartForm.getOrgChartFormElements();
System.out.println(elements);
return "redirect:/spring/finyear/" + finyearId + "/organisationstatus";
}
// expected getters and setters
}
The issue is with the POST handler. I realise that it isn't doing much now, but once I get it to work, I will be persisting the submitted values.
At the moment, the output i see from the two sysout statements are:
Found model attribute: true
[]
Here is my JSP snippet:
<sf:form modelAttribute="orgChartForm" method="post">
<c:forEach items="${orgChartForm.orgChartFormElements}" var="org" varStatus="status">
<sf:hidden id="${org.id}field" path="orgChartFormElements[${status.index}].id"/>
<sf:input id="${org.id}hidden" path="orgChartFormElements[${status.index}].selected"/>
<c:out value="${org.elementLabel}"/>(<c:out value="${org.id}"/>) - <c:out value="${status.index}"/>
</c:forEach>
<input type="submit" value="Submit" />
</sf:form>
When i make the GET request, the JSP renders, and i see my list of text input fields, with the expected values, which tells me that im using the spring-form tags properly. However, when i submit, the form backing object declared as a parameter (orgChartForm) in the POST handler method is initialised, but everything is null/default initialised. I don't know where the submitted data went! It seems that springMVC looses it, and simply constucts a new object.
I have used this pattern extensively in this application without a glitch. It just wont work here. I realise this is a special case in my application where the form field is not atomic but a list, However its really confusing me that the data binds in the GET request, but not on the POST.
Thanks in advance for any pointers!
I think the problem is that you are trying to bind an arbitrary number of form fields to an ArrayList, which is a list that has a predetermined size.
Spring has something called an AutoPopulatingList that is custom designed for this purpose. Please have a look at this link for more info on how to use it: http://blog.richardadamdean.com/?p=12
I think you will need to write PropertyEditorSupport for your class. Following is the example for your reference.
public class SampleEditor extends PropertyEditorSupport {
private final SampleService sampleService;
public SampleEditor(SampleService sampleService, Class collectionType) {
super(collectionType);
this.sampleService = sampleService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
Object obj = getValue();
List list = (List) obj;
for (String str : text.split(",")) {
list.add(sampleService.get(Long.valueOf(str)));
}
}
#Override
public String getAsText() {
return super.getAsText();
}
}
In controller, you should bind it using #InitBinder as follows:
#InitBinder
protected void initBinder(HttpServletRequest request, WebDataBinder binder) {
binder.registerCustomEditor(List.class, "list", new SampleEditor(this.sampleService, List.class));
}
Hope this will solve your problem.

Resources