Why can't I redirect correctly my Java Servlet? [duplicate] - servlets

Does servlet support urls as follows:
/xyz/{value}/test
where value could be replaced by text or number.
How to map that in the web.xml?

It's not supported by Servlet API to have the URL pattern wildcard * in middle of the mapping. It only allows the wildcard * in the end of the mapping like so /prefix/* or in the start of the mapping like so *.suffix.
With the standard allowed URL pattern syntax your best bet is to map it on /xyz/* and extract the path information using HttpServletRequest#getPathInfo().
So, given an <url-pattern>/xyz/*</url-pattern>, here's a basic kickoff example how to extract the path information, null checks and array index out of bounds checks omitted:
String pathInfo = request.getPathInfo(); // /{value}/test
String[] pathParts = pathInfo.split("/");
String part1 = pathParts[1]; // {value}
String part2 = pathParts[2]; // test
// ...
If you want more finer grained control like as possible with Apache HTTPD's mod_rewrite, then you could look at Tuckey's URL rewrite filter or homegrow your own URL rewrite filter.

As others have indicated, the servlet specification does not allow such patterns; however, you might consider JAX-RS which does allow such patterns, if this is appropriate for your use case.
#Path("/xyz/{value}/test")
public class User {
public String doSomething(#PathParam("value") final String value) { ... }
}
Or:
#Path("/xyz/{value}")
public class User {
#Path("test")
public String doTest(#PathParam("value") final String value) { ... }
}
(Related to: https://stackoverflow.com/a/8303767/843093.)

It does support mapping that url; but doesn't offer any validation.
In your web xml, you could do this....
/xyz/*
But that won't guarantee that the trailing test is present and that it is the last item. If you're looking for something more sophisticated, you should try urlrewritefilter.
http://code.google.com/p/urlrewritefilter/

You shouldn't be doing that in web.xml rather you can point every request to your filter (Patternfilter) and can check for URL
package com.inventwheel.filter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
/**
* Servlet Filter implementation class PatternFilter
*/
#WebFilter("/*")
public class PatternFilter implements Filter {
/**
* Default constructor.
*/
public PatternFilter() {
// TODO Auto-generated constructor stub
}
/**
* #see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* #see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String servletPath = ((HttpServletRequest)request).getServletPath();
String requestURI = ((HttpServletRequest)request).getRequestURI();
Pattern pattern = Pattern.compile(".*"+servletPath+"/(.*)");
Matcher matcher = pattern.matcher(requestURI);
if (matcher.matches())
{
String param = matcher.group(1);
// do stuff with param here..
}
chain.doFilter(request, response);
}
/**
* #see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}

As stated above, base servlets does not support patterns like you specified in your question. Spring MVC does support patterns. Here is a link to the pertinent section in the Spring Reference Document.

No Servlet doesn't support patterns like that, possible approach as mentioned by other folks as well is to use /* after xyz but that doesn't check for {value} or /test. Better you go for Spring or JAX-RS. However if you plan to stick with Servlet a better way to write it:
#WebServlet(urlPatterns = {"/xyz/*"})

An answer from the year 2022.
Servlets do still not allow wildcards, so we can't do things like:
/xyz/{value}/test
Paul Tuckeys urlrewritefilter is still in version 4.0.3, and not compatible with the new jakarta namespace [1] (Version 5 is in development).
I found a solution in Tomcat itself, with its feature RewriteValve.
See https://rmannibucau.metawerx.net/post/tomcat-rewrite-url for a step-by-step manual. This is a convenient solution for allowing wildcards in the middle of a URL.
[1] https://github.com/paultuckey/urlrewritefilter/issues/239

Related

Serve static content in Spring Boot despite using #RequestMapping("**")

The context
I am currently working on an educational project. This implies two Spring Boot REST servers. One is an actual server, which does some processing.
The one I'm interested in is the other. It is a proxy which will redirect all calls to the first one. So that when I call http://localhost:8080/foo, my proxy server will in turn call http://localhost:8090/foo. And if the first server returns A, the proxy will return {"proxied": A, "someInformationAboutThisCall": B}.
I managed to get to this point with some probably inelegant but functioning code of which I give an excerpt below. The key here is that I use #RequestMapping("**") to achieve this. The next step is to design an interface that will make my additional information immediately legible, which is basically the point of this project. If I remove all #RequestMapping("**"), it works just fine.
The question
Now my problem is the following: having used #RequestMapping("**"), I cannot serve static content (the calls get redirect to the other REST server, which does not serve static content). How could I configure Spring Boot/Spring MVC to ignore resources available as static content when mapping the requests, or make the PathResourceResolver prioritary over my controller?` Or should I serve my static content from yet another JVM/server?
Thanks in advance for your help!
Edit of interest: while doing some tests, I discovered that the static content is served, with some restrictions, if I use #RequestMapping("*").
/index.html generates an error page (as does more generally any static content directly in public)
/itf/index.html works (as does more generally any file in public/itf or any other subdirectory of public)
/itf does not work: Spring Boot seems unaware of an index file in it. I must specify a full URI, down to the specific file I want to display.
This however does not work at all with #RequestMapping("**"), which I need.
The tentatives
I tried using a WebMvcConfigurerAdapter with an HandlerInterceptorAdapter (found on SO, SO again and many other places on the Internet), but could not start my project anymore because Spring boot then does not find the InterceptorRegistry bean (has there been recent changes in Spring Boot? I'm using the version 1.5.3.RELEASE).
I also tried some anti-matching but not only does it not work, it also feels very very dirty (and this whole project is probably not optimal, so that's saying a lot).
The code samples for the curious
My "proxy" controller
Note: you can suggest better ways to realize this in comments. Please keep in mind that, though I'm always open to enhancement suggestions, this was not my question.
#RestController
public class ProxyController {
#Value("${monitored.url.base}") // "http://localhost:8090"
private String redirectBase;
#RequestMapping(value = "**", method = {RequestMethod.POST, RequestMethod.PUT})
public ProxiedResponse proxifyRequestsWithBody(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
return proxifyRequest(request, headers, body);
}
#RequestMapping(value = "**")
public ProxiedResponse proxifyRequestsWithoutBody(HttpServletRequest request, #RequestHeader HttpHeaders headers) throws URISyntaxException {
return proxifyRequest(request, headers, null);
}
private ProxiedResponse proxifyRequest(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
final RequestEntity<Object> requestEntity = convertToRequestEntity(request, headers, body);
// call remote service
final ResponseEntity<Object> proxied = restTemplate.exchange(requestEntity, Object.class);
// Return service result + monitoring information
final ProxiedResponse response = new ProxiedResponse();
response.setProxied(proxied.getBody());
// set additional information
return response;
}
// Won't work properly for POST yet
private <T> RequestEntity<T> convertToRequestEntity(HttpServletRequest request, HttpHeaders headers, T body) throws URISyntaxException {
// Build proxied URL
final StringBuilder redirectUrl = new StringBuilder(redirectBase).append(request.getRequestURI());
final String queryString = request.getQueryString();
if (queryString != null) {
redirectUrl.append("?").append(queryString);
}
// TODO enhancement: transmit headers and request body to make this a real proxy
final HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
return new RequestEntity<>(body, headers, httpMethod, new URI(redirectUrl.toString()));
}
}
My dirty attempt at excluding static resources URLs
#Configuration // adding #EnableWebMvc did not solve the problem
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
private static class StaticResourcesHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String requestURI = request.getRequestURI();
if (requestURI == null || "/".equals(requestURI) || "/index.html".equals(requestURI) || requestURI.startsWith("/assets")) {
return super.preHandle(request, response, null);
}
return super.preHandle(request, response, handler);
}
}
#Autowired
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new StaticResourcesHandlerInterceptor()).addPathPatterns("/**");
}
}
You can split the path into a wild-card, and a named path variable which must match a negative lookahead regular expression.
#RequestMapping("/{variable:(?!static).*}/**")
You can then use #PathVariable String variable as an argument of your controller method to obtain the value of variable if you need to pass it.
(Would rather have written a comment but I have insufficient reputation)
Try to add the #EnableWebMvc annotation to your configuration:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
...
}

Eclipse Scout Neon mock backend service

In our project I have modules scout.client, scout.server, scout.shared and backend.
Backend has no dependencies to scout.server and scout.shared, but scout.server has dependencies to backend.
Inside backend project I have all business logic and calling all outside services.
My problem is when I try to test scout services that use some service from backend.
Because scout provide some great tool for mocking beans, we defined our service inside backend as beans as :
BEANS.getBeanManager().registerClass(CarService.class);
BEANS.getBeanManager().registerClass(PartnerService.class);
Both, CarService.class and PartnerService.class are in backend.
When I try to write some tests and I add #BeanMock to service in test
#BeanMock
private IPartnerService partnerService;
I get mock, but then every return every function is null, even if I write
doReturn(PartnerBuilder.standardPartnerListWithOneElement()).when(this.partnerService)
.getPartners(any(Set.class));
If I debug in my test, before this test is called with debugger I can get :
partnerService.getPartners(...) -> return a list of person
what is right, but when class that is tested calles this service it return null.
I understand that this could be due to missing annotation on interface #ApplicationScoped. Without this there is no guarantee that only one bean is created, and when statement react on another copy of that bean...?
I could not add annotation on interface because backend has no dependencies to scout modules.
How could I handle this kind of cases?
Tested class is :
public class UtilityPartner {
/**
* Method return service bean for getting partners by ids.
*
* #return
*/
private static IPartnerService getPartnerService() {
return BEANS.get(IPartnerService.class);
}
public static String getPartnerName(final Long partnerId) {
if (partnerId == null) {
return "";
}
final List<Partner> partners =
(List<Partner>) getPartnerService().getPartners(Sets.newHashSet(partnerId));
if (partners == null || partners.isEmpty()) {
return "";
}
final Partner partner = partners.get(0);
return LookupUtil.createLookupDescription(partner.getId(), partner.getName());
}
}
test class is :
#RunWith(ServerTestRunner.class)
#RunWithSubject("anonymous")
#RunWithServerSession(ServerSession.class)
public class TestUtilityPartner {
#BeanMock
private IPartnerService partnerService;
#Before
public void init() {
doReturn(PartnerBuilder.standardPartnerListWithOneElement()).when(this.partnerService).getPartners(any(Set.class));
}
#Test
public void getPartnerName() {
final String name = UtilityPartner.getPartnerName(10L);
Assert.assertEquals("My name", name); // NAME IS ""
}
}
Using #BeanMock does not help here, because you are not using an application scoped service:
In the init method you are changing the local field partnerService. However, in your test you call UtilityPartner.getPartnerService, which is creating a new instance (with BEANS.get(IPartnerService.class)).
#BeanMock is more useful for convenience for mocking application scoped beans.
You can always register your beans manually as shown by Jmini. Please do not forget to unregister the bean again after the test!
We recommend using org.eclipse.scout.rt.testing.shared.TestingUtility.registerBean(BeanMetaData), which is automatically adding a testing order and removing #TunnelToServer annotations.
I think that you should register your mock instance in the Bean manager (See bean registration in the Scout Architecture Document). You should use a small order (-10 000 is recommended for tests), in order for your mock to win over the productive registration. The best approach is to use the TestingUtility class to register/unregister your mock. Do not forget to call the unregisterBean() method (in the method annotated with #After):
import java.util.Collections;
import org.eclipse.scout.rt.platform.BeanMetaData;
import org.eclipse.scout.rt.platform.IBean;
import org.eclipse.scout.rt.testing.shared.TestingUtility;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class TestUtilityPartner {
private IBean<?> beanRegistration;
#Before
public void init() {
partnerService = Mockito.mock(IPartnerService.class);
// Register the mock using the Bean meta information:
BeanMetaData beanData = new BeanMetaData(IPartnerService.class)
.withInitialInstance(partnerService)
.withApplicationScoped(true);
this.beanRegistration = TestingUtility.registerBean(beanData);
// Mockito behavior:
Mockito.doReturn(Collections.singletonList(new Partner(34L, "John Smith")))
.when(partnerService).getPartners(Mockito.any(Set.class));
}
#After
public void after() {
// Unregister the mocked services:
TestingUtility.unregisterBean(this.beanRegistration);
}
#Test
public void getPartnerName() {
String name = UtilityPartner.getPartnerName(10L);
Assert.assertEquals("10 - John Smith", name);
}
}
I am not sure what #BeanMock (org.eclipse.scout.rt.testing.platform.mock.BeanMock) is doing, but according to Judith Gull's answer it will not work:
Using #BeanMock does not help here, because you are not using an application scoped service:
In the init method you are changing the local field partnerService. However, in your test you call UtilityPartner.getPartnerService, which is creating a new instance (with BEANS.get(IPartnerService.class)).
#BeanMock is more useful for convenience for mocking application scoped beans.

Minecraft modding block constructer error

I'm making a mod, and I am getting an error(no duh) and I have tried searching it up but I want an answer specific to my problem because I am not very good at this. I am getting this error in my block class.
Implicit super constructor Block() is undefined for default constructor. Must define an explicit constructor
and I don't know how to fix it. Please Help its for a project.
block class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
public class BlockCbBlock extends Block {
protected BlockCbBlock(Material material) {
super(material);
}
}
mod class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
#Mod(modid = "fc", name = "Fandomcraft", version = "1.0")
public class fandomcraft {
public static Item itemCbIngot;
public static Block blockCbBlock;
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Item/Block initialization and registering
//Config Handling
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot"); //item.itemCbIngot.name
blockCbBlock = new BlockCbBlock(Material.iron);
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}
#EventHandler
public void init(FMLInitializationEvent event){
//Proxy, TileEntity, entity, GUI and Packet Registering
}
#EventHandler
public void postInit(FMLPostInitializationEvent event) {
}
}
This error pertains to all of java, not just minecraft forge. Check this for some more reference. There are a couple possible reasons for this error. It is most likely 1, but 2 and 3 can be a contributing factor to the error.
Your BlockCbBlock Class declares a constructor that is not the default, no-argument constructor that the compiler would otherwise provide (that is, if the Block class doesn't have a constructor) and, if in fact the Block class is using the default constructor, then you can't call super() on the arguements because the Block class uses a constructor with no arguments. Because of this, if you wanted to modify the Block constructor, it would be safier and easier to create a custom construcotr inside of the BlockCbBlock class itself.
You are trying to inherit the constructor of Block, but you have declared it as protected, when the constructor in your class should be public to match the inherited .
If you're using Eclipse, it can give this error when you have your project setup incorrectly (system configuration mismatch)
Probably not directly realted to this specific error, but a possible cause of other errors in the near future; you are using the annotation #EventHandler, but you have not actually declared the forge event handler.
You don't actually register the block for some reason. Even if you're using the block as a recipe item, you still need to register it
To fix potential problems 1, 2, and 4, try this (obtained from here):
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
private final String name = "BlockCbBlock";
public class BlockCbBlock extends Block {
public BlockCbBlock() {
super(Material.iron);
GameRegistry.registerBlock(this, name);
setUnlocalizedName(Reference.MODID + "_" + name);
setCreativeTab(CreativeTabs.tabBlock);
}
public String getName() {
return name;
}
}
This way, you'll declare its UnlocalizedName, Material, and CreativeTab ahead of time. This method might be unnecessary, but its a good precaution to help prevent the error. Now, all you have to do is declare it like this:
//You need to make your own EventHandler class. Search online for that.
FCEventHandler handler = new FCEventHandler();
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Config Handling
//event handler registry
FMLCommonHandler.instance().bus().register(handler);
MinecraftForge.EVENT_BUS.register(handler);
//the same thing can be similarly done with this if you wish
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot");
blockCbBlock = new BlockCbBlock();
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}

Unable to write a Filter which serves for Login

I have written a filter and declared it under web.xml as shown below
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<filter>
<display-name>SessionAuthenticationFilter</display-name>
<filter-name>SessionAuthenticationFilter</filter-name>
<filter-class>com.jsp.auth.SessionAuthenticationFilter</filter-class>
<init-param>
<param-name>skipthis</param-name>
<param-value>01-login.html</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SessionAuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
And this is my FILTER File
package com.jsp.auth;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionAuthenticationFilter implements Filter {
private FilterConfig filterConfig = null;
public SessionAuthenticationFilter() {
}
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(true);
String loginpageuri = filterConfig.getInitParameter("skipthis");
String requesturi = request.getRequestURI();
System.out.println("The requesturi is"+requesturi);
if(!requesturi.endsWith(loginpageuri))
{
String isloggoed = (String)session.getAttribute("LOGIN_USER");
System.out.println("The isloggoed value is"+isloggoed);
if(isloggoed==null||isloggoed.equals("")||isloggoed.isEmpty())
{
response.sendRedirect("http://xxx.xx.xx:8080/admin/01-login.html");
return ;
}
else
{
chain.doFilter(req, res);
}
}
else
{
chain.doFilter(req, res);
}
}
public void init(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
}
These are the logs i am getting in my server console . (Its blocking all the .css .js files also)
The isloggoed value isnull
The requesturi is/AdminE/assets/css/style-metro.css
The isloggoed value isnull
The requesturi is/AdminE/assets/css/style-responsive.css
The isloggoed value isnull
The requesturi is/AdminE/assets/css/themes/default.css
The isloggoed value isnull
Incase if i change the url-pattern to this way in my web.xml
/*.html
I am getting HTTP Status 404 - /AdminE/01-login.html
Could anybody please help me how to resolve this ?
According to the Java Servlet Specification, the url-pattern should be:
A string beginning with a ‘/’ character and ending with a ‘/*’ suffix is used for path mapping.
A string beginning with a ‘*.’ prefix is used as an extension mapping.
The empty string ("") is a special URL pattern that exactly maps to the application's context root, i.e., requests of the form
http://host:port/<contextroot>/. In this case the path info is ’/’ and
the servlet path and context path is empty string ("").
A string containing only the ’/’ character indicates the "default" servlet of the application. In this case the servlet path is the
request URI minus the context path and the path info is null.
All other strings are used for exact matches only.
So according to this, the pattern /*.html will be interpreted literally and the * will not act as a wildcard. Try with *.html instead (no leading slash).
If I correctly understand your question, you are trying to use a filter as a login service protecting a whole application. If you do not want to re-invent the wheel, you could also have a look to the excellent shiro or Spring security.
If you prefer to roll your own, you should give your filter more parameters :
the address of the login page (it must let it pass through and redirects to it when not previously logged) - ok you have it
a list of patterns to ignore because the servlet specification (see David Levesque's answer) is not versatile enough - alternatively you can use a positive logic giving a list of patterns to filter. It will be much simpler to use simple extensions (.js, .css, .gif, ...) than full patterns because then, String.endsWith is enough.
I also advise you to do all the parameters management in Filter.init method instead of repeating it for every request. Typically in your code, the attribute should be String loginpageuri, and init method should be :
public void init(FilterConfig filterConfig) {
loginpageuri = filterConfig.getInitParameter("skipthis");
}

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.

Resources