Search code examples
jsf-2jarresourcesversioning

How to use JSF versioning for resources in jar


PF 3.5.10, Mojarra 2.1.21, omnifaces 1.5

I have a JSF library (with css files only). This library is in a .jar file. The css will be included in xhtml with <h:outputStylesheet library="mylib" name="css/mycss.css">.

In html it is rendered to the following: localhost:8080/cms/javax.faces.resource/css/mycss.css.jsf?ln=mylib

CSS file of primefaces is rendered to: localhost:8080/cms/javax.faces.resource/primefaces.js.jsf?ln=primefaces&v=3.5.10

Notice the library version (&3.5.10) at the end. How can I do the same thing ? Should I write version in Manifest.mf. Or how can I use jsf-versioning in jar file?


Solution

  • That's unfortunately not possible. Library versioning is not supported for resources in JAR.

    You've basically 2 options:

    1. Do it the easy and ugly way, include server's startup time as query string. Given that you're using OmniFaces, you could use its builtin #{startup} managed bean referring a java.util.Date instance in application scope:

      <h:outputStylesheet ... name="some.css?#{startup.time}" />
      <h:outputScript ... name="some.js?#{startup.time}" />
      

      Or perhaps you've the version already as some application variable.

      <h:outputStylesheet ... name="some.css?v=#{app.version}" />
      <h:outputScript ... name="some.js?v=#{app.version}" />
      

      Update: Notwithstanding, this doesn't work for <h:outputStylesheet>. See also: https://github.com/javaserverfaces/mojarra/issues/3945 or https://github.com/javaee/javaserverfaces-spec/issues/1395

      It works for <h:outputScript> though, which had a very simliar bug report which was implemented pretty soon https://github.com/javaserverfaces/mojarra/issues/1216

    2. Do the same as PrimeFaces, create a custom ResourceHandler.

      public class MyVersionResourceHandler extends ResourceHandlerWrapper {
      
          private ResourceHandler wrapped;
      
          public MyVersionResourceHandler(ResourceHandler wrapped) {
              this.wrapped = wrapped;
          }
      
          @Override
          public Resource createResource(String resourceName) {
              return createResource(resourceName, null, null);
          }
      
          @Override
          public Resource createResource(String resourceName, String libraryName) {
              return createResource(resourceName, libraryName, null);
          }
      
          @Override
          public Resource createResource(String resourceName, String libraryName, String contentType) {
              final Resource resource = super.createResource(resourceName, libraryName, contentType);
      
              if (resource == null) {
                  return null;
              }
      
              return new ResourceWrapper() {
      
                  @Override
                  public String getRequestPath() {
                      return super.getRequestPath() + "&v=1.0";
                  }
      
                  @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
                  public String getResourceName() {
                      return resource.getResourceName();
                  }
      
                  @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
                  public String getLibraryName() {
                      return resource.getLibraryName();
                  }
      
                  @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
                  public String getContentType() {
                      return resource.getContentType();
                  }
      
                  @Override
                  public Resource getWrapped() {
                      return resource;
                  }
              };
          }
      
          @Override
          public ResourceHandler getWrapped() {
              return wrapped;
          }
      
      }
      

      Or if you happen to already use OmniFaces, it could be done simpler:

      public class YourVersionResourceHandler extends DefaultResourceHandler {
      
          public YourVersionResourceHandler(ResourceHandler wrapped) {
              super(wrapped);
          }
      
          @Override
          public Resource decorateResource(Resource resource) {
              if (resource == null || !"mylib".equals(resource.getLibraryName())) {
                  return resource;
              }
      
              return new RemappedResource(resource, resource.getRequestPath() + "&v=1.0");
          }
      
      }
      

      Either way, to get it to run, register it as <resource-handler> in /META-INF/faces-config.xml of the JAR.

      <application>
          <resource-handler>com.example.MyVersionResourceHandler</resource-handler>
      </application>