Before to start we have to confess that afer many years of experience we sincerely dislike JSF technology, as we think it's outdated compared to html 5 + REST.
We have a JSF 2.2 application, which is configured to track session through url. In this case Session ID is stored in url and not in cookies, as there may be many sessions opened per a client.
At the same time application uses libraries that expose scripts and css resources. This resources are referred to like this:
<link rel="stylesheet" type="text/css" jsfc="h:outputStylesheet" library="css" name="library-name.css"/> <script type="text/javascript" jsfc="h:outputScript" name="library-name.js" library="scripts" target="head"></script>
At runtime this is rendered as:
<link type="text/css" rel="stylesheet" href="/App/javax.faces.resource/library-name.css.jsf;jsessionid=FC4A893330CCE12E8E20DFAFC73CDF35?ln=css" /> <script type="text/javascript" src="/App/javax.faces.resource/library-name.js.jsf;jsessionid=FC4A893330CCE12E8E20DFAFC73CDF35?ln=scripts"></script>
You can see that Session ID is a part of url path, which prevents resource caching on a client.
It's not clear whether it's what JSF spec dictates or it's Oracle's Reference Implementation detail. We're certain, however, that it's too wasteful in heavy loaded environment, so we have tried to resolve the problem.
From JSF sources we have found that h:outputStylesheet, h:outputScript, and h:outputLink all use ExternalContext.encodeResourceURL() method to build markup url.
h:outputStylesheet
h:outputScript
h:outputLink
ExternalContext.encodeResourceURL()
So, here is a solution: to provide custom wrapper for the ExternalContext.
ExternalContext
This is done in two steps:
1. Factory is a simple class but unfortunately it's implementation specific:
package com.nesterovskyBros.jsf; import javax.faces.FacesException; import javax.faces.context.ExternalContext; import javax.faces.context.ExternalContextWrapper; import com.sun.faces.context.ExternalContextFactoryImpl; /** * {@link ExternalContextFactory} to prevent session id in resource urls. */ public class ExternalContextFactory extends ExternalContextFactoryImpl { /** * {@inheritDoc} */ @Override public ExternalContext getExternalContext( Object context, Object request, Object response) throws FacesException { final ExternalContext externalContext = super.getExternalContext(context, request, response); return new ExternalContextWrapper() { @Override public ExternalContext getWrapped() { return externalContext; } @Override public String encodeResourceURL(String url) { return shouldEncode(url) ? super.encodeResourceURL(url) : url; } private boolean shouldEncode(String url) { // Decide here whether you want to encode url. // E.g. in case of h:outputLink you may want to have session id in url, // so your decision is based on some marker (like &session=1) in url. return false; } }; } }
2. Registration is just three lines in faces-config.xml:
faces-config.xml
<factory> <external-context-factory>com.nesterovskyBros.jsf.ExternalContextFactory</external-context-factory> </factory>
After that change at runtime we have:
<link type="text/css" rel="stylesheet" href="/App/javax.faces.resource/library-name.css.jsf?ln=css" /> <script type="text/javascript" src="/App/javax.faces.resource/library-name.js.jsf?ln=scripts"></script>