18

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?

Tony
  • 2,266
  • 4
  • 33
  • 54
  • possible duplicate of [How to specify resource library version in JSF2?](http://stackoverflow.com/questions/9868068/how-to-specify-resource-library-version-in-jsf2) – Aritz Aug 09 '13 at 10:09
  • No, I think this not a duplicate because the question is about jar file. In jar this is different. See BalusC answer: [link](http://stackoverflow.com/a/11988418/2023524) – Tony Aug 09 '13 at 11:19

3 Answers3

25

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>
    
YoYo
  • 9,157
  • 8
  • 57
  • 74
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thank you! Very helpful answer. Your idea in 1 `#{startup}` is nice. So client css will be always updated and you can not forget to change the version. Another question: Is it possible to place this ResourceHandler in jar file and use it from there ? So every jar will be independent. – Tony Aug 09 '13 at 12:46
  • 2
    Yes, put class in JAR and register it in `/META-INF/faces-config.xml` of JAR. This is also mentioned in bottom of answer, for the case you missed it. – BalusC Aug 09 '13 at 12:49
  • Thank you! I am a big fan of your tutorials. They help a lot. – Tony Aug 09 '13 at 12:52
  • 1
    Excellent answer as usual. However I found that com.sun.faces.application.resource.ResourceImpl will use the v parameter for libraries with version info. So I made a slight change to this great example by change the parameter name. I'm using the excellent https://github.com/ktoso/maven-git-commit-id-plugin to generate a git commit hash with gch as the parameter name. – byeo Jan 17 '14 at 10:21
  • I've also corrected the parameter connective using a & where libraries have been added to the path already and ? where no parameters exist on the path. https://gist.github.com/olibye/8471705 – byeo Jan 17 '14 at 11:05
  • I have taken your ideas and realized it as jsf2.2 at https://github.com/SchulteMarkus/Megan/blob/master/src/main/java/com/megan/faces/application/MeganStaticFilesResourceHandlerWrapper.java - it takes the original idea from BalusC combined with maven-git-commit-id-plugin. As addition, it does not versionate primefaces-static-files or files from library "javax.faces". – Markus Schulte Oct 29 '14 at 10:05
  • Is this still true for JSF 2.2? Is library versioning now supported for resources in JAR? – raupach Jan 25 '16 at 15:37
  • 1
    @raupach: It's a technical limitation. Now you come back at it after years, if you happen to already use OmniFaces, look here how you could do it with less code: https://github.com/omnifaces/omnifaces/blob/master/src/main/java/org/omnifaces/resourcehandler/OmniVersionResourceHandler.java – BalusC Jan 25 '16 at 15:41
  • @BalusC Was just curious. Thanks for the quick answer. – raupach Jan 25 '16 at 15:51
  • @BalusC I like the idea with appending the startup time (or a version) to the resources. For javascript files it works, but I have the problem that css files are not found when i manually append something. Do you have an idea why this could be the case? – marcel Mar 08 '16 at 09:52
  • @marcel: bug in Mojarra. – BalusC Mar 08 '16 at 10:05
  • @BalusC Thank-you! Sounds like you know it´s a bug, or do I misinterpret it? – marcel Mar 08 '16 at 10:50
  • 1
    @marcel: well, not a bug, but oversight and spec fail. See https://java.net/jira/browse/JAVASERVERFACES-3941 and https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1395 – BalusC Mar 08 '16 at 10:57
  • @BalusC using a custom ResourceHandler, is not possible to change the version at runtime (e.g. pulling the version code from a DB variable), without restarting server, due to resourceHandler caching. Is there any solution to this problem? http://stackoverflow.com/questions/42886630/programmatically-clear-jsf-resource-handler-cache –  Mar 19 '17 at 14:55
  • ResourceHandler doesn't cache on server side at all, so I don't understand your problem in first place. You might want to reinvestigate your problem and reframe your question. – BalusC Mar 19 '17 at 15:56
3

You can also use your project version and append it as a version number for your resource files. This can be done using the maven-war-plugin. The maven-war-plugin will look at your pages during the build time and replace the defined properties.

The following example shows you how to configure the maven-war-plugin to filter your webapp resources in order to inject the custom property asset.version:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  ...

  <properties>
    <asset.version>${project.version}</asset.version>    
  </properties>

  ...

  <build>
    <plugins>
      ...

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>

          <nonFilteredFileExtensions>
            <nonFilteredFileExtension>gif</nonFilteredFileExtension>
            <nonFilteredFileExtension>ico</nonFilteredFileExtension>
            <nonFilteredFileExtension>jpg</nonFilteredFileExtension>
            <nonFilteredFileExtension>png</nonFilteredFileExtension>
            <nonFilteredFileExtension>pdf</nonFilteredFileExtension>
          </nonFilteredFileExtensions>

          <failOnMissingWebXml>false</failOnMissingWebXml>

          <webResources> 
            <webResource>
              <directory>${basedir}/src/main/webapp</directory> 
              <filtering>true</filtering> 
            </webResource>
          </webResources> 

        </configuration>
      </plugin>

      ...
    </plugins>
  </build>

</project>

The asset.version property can then be used in your JSF file.

Here is an example tested with JSF 2.2:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html ...
      xmlns:jsf="http://xmlns.jcp.org/jsf">

...
<script jsf:name="js/libs/pure/pure-min.css?v=${project.version}" />

The result (in my case) will be the following:

<script type="text/javascript" src="/context-path/javax.faces.resource/js/libs/pure/pure-min.css.xhtml?v=1.0.15-SNAPSHOT"></script>
Benny Code
  • 51,456
  • 28
  • 233
  • 198
  • 1
    Good point! Thank you. It is interesting solution who uses Maven. We unfortunately don't do it. – Tony Jul 08 '14 at 11:39
0

@Balusc response said "well, not a bug, but oversight and spec fail". It seems like css resources deployed in libraries cannot be versioned with mojarra 2.2.14. Is it right? I tried to implement your solution with a custom ResourceHandler, but resource returned by getWrapped().createResource(resourceName, libraryName) always returns null. It seems like createResource() try to find the library's resources (like css/layout.css) with path /META-INF/resources/ but it lacks the version.

To workaround the problem i have overrided createResource method on a custom ResourceHandler which extends Omnifaces DefaultResourceHandler to add version prefix to the resourceName

@Override
public Resource createResource(String resourceName, String libraryName) {
    if (libraryName != null && libraryName.equals(LIBRARY_NAME)) {
        if (!resourceName.startsWith(version)) {
            resourceName = version + "/"+resourceName;
        }
    }
    return super.createResource(resourceName, libraryName);
}

With this workaround the generated link looks like

<link type="text/css" rel="stylesheet" href="/javax.faces.resource/1_0_3/css/layout.css?ln=common&amp;v=1_0_3"/>

for the outputStylesheet declaration

<h:outputStylesheet library="common" name="css/layout.css" />

I'm not sure this is the best workaround.

jpl
  • 347
  • 3
  • 11