I am using PrimeFaces 12 in my JSF application and have a custom theme. The theme is included automatically by PrimeFaces, and its resource URL looks like this:
<link type="text/css" rel="stylesheet" href="/GuiApp/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-custom&v=12.0.0">
The problem is that when I update the theme's CSS, users often see the old cached version because the browser does not fetch the updated file. To solve this, I want to add a dynamic version number to the theme URL, such as v=12.0.1, so that the browser always fetches the latest version.
<context-param>
<param-name>theme.version</param-name>
<param-value>12.0.1</param-value>
</context-param>
@Override
public Resource createResource(String resourceName, String libraryName) {
if ("primefaces-custom".equals(libraryName) && "theme.css".equals(resourceName)) {
Resource resource = super.createResource(resourceName, libraryName);
String version = FacesContext.getCurrentInstance()
.getExternalContext()
.getInitParameter("theme.version");
return new VersionedResource(resource, version);
}
return super.createResource(resourceName, libraryName);
}
@Override
public String getRequestPath() {
String path = wrapped.getRequestPath();
if (path.contains("?")) {
path = path.replaceAll("[&?]v=[^&]*", ""); // Remove existing version
}
return path + (path.contains("?") ? "&" : "?") + "v=" + version;
}
When I implemented this, the theme URL was updated, but it included duplicate version parameters like this:
<link type="text/css" rel="stylesheet" href="/GuiApp/jakarta.faces.resource/theme.css.xhtml?ln=primefaces-custom&v=12.0.0&v=12.0.1">
After deploying, my application broke, and some resources stopped loading.
Here is my Step-by-Step Solution:
context-param
in web.xml
to define the theme version:In your web.xml
, define a context-param
to specify the theme version you want to use.
<context-param>
<param-name>theme.version</param-name>
<param-value>12.0.1</param-value> <!-- Update this version when you release a new one -->
</context-param>
Next, extend the default ViewHandler and override the createResource method. This method will dynamically set the version of the theme's CSS URL by fetching the version from the web.xml.
public class CustomResourceHandler extends ViewHandler {
private ViewHandler parent;
public CustomResourceHandler(ViewHandler parent) {
this.parent = parent;
}
// Method to get the theme version from web.xml
private String getThemeVersion(FacesContext context) {
String version = context.getExternalContext().getInitParameter("theme.version");
return (version != null) ? version : "12.0.0"; // Default version if not set
}
@Override
public Resource createResource(String resourceName, String libraryName) {
if ("primefaces-custom".equals(libraryName) && "theme.css".equals(resourceName)) {
Resource resource = super.createResource(resourceName, libraryName);
if (resource != null) {
FacesContext context = FacesContext.getCurrentInstance();
String themeVersion = getThemeVersion(context);
// Dynamically update the resource path with the version
String newPath = resource.getRequestPath().replaceAll("&v=[0-9.]+", "&v=" + themeVersion);
return new ResourceWrapper(resource) {
@Override
public String getRequestPath() {
return newPath;
}
};
}
}
return super.createResource(resourceName, libraryName);
}
}
VersionedResource
to Modify the URLimport jakarta.faces.application.Resource;
import jakarta.faces.context.FacesContext;
public class VersionedResource extends Resource {
private final Resource wrapped;
private final String version;
public VersionedResource(Resource wrapped, String version) {
this.wrapped = wrapped;
this.version = version;
}
@Override
public String getRequestPath() {
String path = wrapped.getRequestPath();
if (path.contains("?")) {
path = path.replaceAll("[&?]v=[^&]*", "");
}
if (path.contains("?")) {
return path + "&v=" + version;
} else {
return path + "?v=" + version;
}
}
@Override
public InputStream getInputStream() throws IOException {
return wrapped.getInputStream();
}
@Override
public Map<String, String> getResponseHeaders() {
return wrapped.getResponseHeaders();
}
@Override
public String getContentType() {
return wrapped.getContentType();
}
@Override
public String getLibraryName() {
return wrapped.getLibraryName();
}
@Override
public String getResourceName() {
return wrapped.getResourceName();
}
@Override
public URL getURL()
{
// TODO Auto-generated method stub
return null;
}
@Override
public boolean userAgentNeedsUpdate(FacesContext context)
{
// TODO Auto-generated method stub
return false;
}
}
<application>
<view-handler>com.yourpackage.CustomResourceHandler</view-handler>
</application>