JDK-8192748 : Multi-release feature fails when WebStart cache is disabled
  • Type: Bug
  • Component: deploy
  • Affected Version: 9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86
  • Submitted: 2017-11-29
  • Updated: 2017-12-15
  • Resolved: 2017-12-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 10
10 b36Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
When starting a multi-release application with Java 9 WebStart the classes located in META-INF/versions/9 will be ignored when the WebStart application cache is disabled. As soon as the WebStart cache is enabled it works fine.

A simple test can be found below, you can also give it a try by executing http://www.jyloo.com/downloads/public/test/multiReleaseTest.jnlp

Test case:
----------------------------------
package mrtest;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

import mrtest.impl.Implementation;

public class Main extends JFrame{
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run(){
         new Main(); 
      }
    });
  }

  public Main(){
    add(new JLabel(new Implementation().toString()));    
    setTitle("MultiRelease Test");    
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(800, 600);
    setLocationRelativeTo(null);
    setVisible(true);
  }  
}

----------------------------------
//Default implementation class
package mrtest.impl;
public class Implementation{  
  @Override
  public String toString() {
    return "Default Implementation";
  }
}

----------------------------------
//Java 9 implementation class in META-INF/versions/9
package mrtest.impl;
public class Implementation{  
  @Override
  public String toString(){
    return "***** Java 9 implementation *****";
  }
}
Comments
Crucivle review: https://java.se.oracle.com/code/cru/CR-JDK10CLIENT-154
11-12-2017

recent fix to JDK-8132734 in JDK10 has fixed this for the case where JDK10 deploy code is running with JDK10 JRE. Remaining broken case is where JDK10 Deploy code is running with JDK9 JRE. This could be fixed with the following patch to DeployURLConnection: DeployURLConnection(URL url, sun.net.www.protocol.jar.Handler handler, boolean ignoreClassPath, boolean useVersionProtocol) throws MalformedURLException, IOException { - super(url, handler); + super(getAnchoredURL(url), handler); _ignoreClassPath = ignoreClassPath; _useVersionProtocol = useVersionProtocol; // Obtain jar file URL @@ -86,6 +86,19 @@ _entryName = getEntryName(); } + private static URL getAnchoredURL(URL url) { + // only add runtime anchor for Multi-Version URL if JRE is 9 + if (!Config.isJavaVersionAtLeast10() && + Config.isJavaVersionAtLeast9()) { + try { + return new URL(url.toString() + "#runtime"); + } catch (Exception e) { + // ignore + } + } + return url; + } +
08-12-2017

simple test case at http://oklahoma.us.oracle.com/www/tests/multi/test.jnlp with separate versions for 9, 10, and default works fine when appending #runtime anchor to the url passed by DeployURLConnection to it's super class sun.net.www.protocol.jar.JarURLConnection. otherwise only works with caching enabled.
01-12-2017

ok - the problem is ultimately caused by the following code in sun.net.www.protocol.jar.URLJarFile.retrieve(): Runtime.Version ver = "runtime".equals(url.getRef()) ? JarFile.runtimeVersion() : JarFile.baseVersion(); without specifically having a "#runtime" anchor on the url, it will create a jar file with version 8 (JarFile.baseVersion()) instead of whatever version you are actually running (in my case JarFile.runtimeVersion() is 10) this is in boith getJarFile() and retrieve() in URLJarFile and was last modified by JDK-8150680 , but same basic logic seems to predate that, going back to the original implementation of JDK-8132734
01-12-2017

OK - backdoor to getRealEntry won't work - that is what we get anyway
01-12-2017

The difference between cached and uncached jars seems to be due to the code in DeployURLClassPath.checkResource(): String nm = name; JarEntry realEntry = entry; if (Config.isJavaVersionAtLeast9() && jar.isMultiRelease()) { if (jar instanceof CachedJarFile) { realEntry = (JarEntry) ((CachedJarFile) jar).getRealEntry(name); } try { nm = DeployJavaUtilJarAccess.instance().getRealName( jar, realEntry); } ... url = new URL(getBaseURL(), parseUtilEncodePath(nm, false)); in this case, when cache is enabled, the jar is an instance of a CachedJarFile. the JarEntry passed in has name "mrtest/impl/Implementation.class" but the name returned by DeployJavaUtilJarAccess.instance().getRealName(jar, ((CachedJarFile) jar).getRealEntry("mrtest/impl/Implementation.class")) is "META-INF/versions/9/mrtest/impl/Implementation.class". we need this "real name" for the following code to work, since we return a resource who's getURL() method returns the above "url". The only backdoor available thru JavaUtilJarAccess for Multi-Version jars is the above "getRealName" that takes a jar and a jar entry, and that only returns the versioned name if it is passed in the "realEntry", which I assume is the cache entry directly referring to "META-INF/versions/9/mrtest/impl/Implementation.class" So we either need a backdoor to the private "JarFile.getVersionedEntry()" for use with vanilla JarFile objects, or even simpler we need a backdoor to JarFile.getRealName(). I'm also not really sure how the CachedJarFile case is working. CachedJarFile.getRealEntry is: if (Config.isJavaVersionAtLeast9() && isMultiRelease()) { return super.getEntry(name); } else { return getEntry(name); } but super.getEntry() is just JarFile.getEntry(name ) which essentially returns new JarFileEntry(super.getEntry(name)) cachedJarFile.getEntry is essentially returning new JarFileEntry(super.getEntry()) (it's own getJarFileEntry in this case) so it is: new CachedJarFile.JarFileEntry((new JarFile.JarFileEntry).getEntry(name))
30-11-2017

strange - the applet example at http://oklahoma.us.oracle.com/www/tests/multi/test.html (which fails when moved locally and run with file protocol), works with both cache enabled and cache disabled. This example (webstart app at http://www.jyloo.com/downloads/public/test/multiReleaseTest.jnlp ) works when cache is enabled and fails when cache is disabled. - so problem may not be identical.
30-11-2017

This is likely underlying cause of JDK-8191541. In that case, multi release feature doesn't work when using file protocol. - resources using file protocol are not cached, so this gives clue to what underlying reason is.
30-11-2017