|
14 | 14 | import java.io.InputStreamReader; |
15 | 15 | import java.io.UncheckedIOException; |
16 | 16 | import java.lang.module.ModuleFinder; |
| 17 | +import java.net.MalformedURLException; |
17 | 18 | import java.net.URI; |
18 | 19 | import java.net.URISyntaxException; |
19 | 20 | import java.net.URL; |
|
26 | 27 | import java.security.CodeSource; |
27 | 28 | import java.security.PrivilegedAction; |
28 | 29 | import java.security.SecureClassLoader; |
| 30 | +import java.util.ArrayList; |
29 | 31 | import java.util.Collections; |
30 | 32 | import java.util.Enumeration; |
31 | | -import java.util.HashMap; |
| 33 | +import java.util.LinkedHashMap; |
32 | 34 | import java.util.List; |
33 | 35 | import java.util.Map; |
34 | 36 | import java.util.NoSuchElementException; |
35 | 37 | import java.util.Objects; |
36 | 38 | import java.util.function.Function; |
| 39 | +import java.util.jar.Manifest; |
| 40 | + |
| 41 | +import static java.util.jar.Attributes.Name.MULTI_RELEASE; |
37 | 42 |
|
38 | 43 | /** |
39 | 44 | * A class loader that is responsible for loading implementation classes and resources embedded within an archive. |
@@ -67,7 +72,7 @@ public final class EmbeddedImplClassLoader extends SecureClassLoader { |
67 | 72 | private final Map<String, CodeSource> prefixToCodeBase; |
68 | 73 |
|
69 | 74 | private static final String IMPL_PREFIX = "IMPL-JARS/"; |
70 | | - private static final String MANIFEST_FILE = "/LISTING.TXT"; |
| 75 | + private static final String JAR_LISTING_FILE = "/LISTING.TXT"; |
71 | 76 |
|
72 | 77 | static EmbeddedImplClassLoader getInstance(ClassLoader parent, String providerName) { |
73 | 78 | return new EmbeddedImplClassLoader(parent, getProviderPrefixes(parent, providerName)); |
@@ -152,6 +157,8 @@ protected Enumeration<URL> findResources(String name) throws IOException { |
152 | 157 | return new CompoundEnumeration<>(tmp); |
153 | 158 | } |
154 | 159 |
|
| 160 | + // -- modules |
| 161 | + |
155 | 162 | /** |
156 | 163 | * Returns a module finder capable of finding the modules that are loadable by this embedded impl class loader. |
157 | 164 | * |
@@ -208,26 +215,68 @@ private static URI rootURI(URL url) { |
208 | 215 |
|
209 | 216 | private static Map<String, CodeSource> getProviderPrefixes(ClassLoader parent, String providerName) { |
210 | 217 | String providerPrefix = IMPL_PREFIX + providerName; |
211 | | - URL manifest = parent.getResource(providerPrefix + MANIFEST_FILE); |
212 | | - if (manifest == null) { |
| 218 | + URL listingURL = parent.getResource(providerPrefix + JAR_LISTING_FILE); |
| 219 | + if (listingURL == null) { |
213 | 220 | throw new IllegalStateException("missing x-content provider jars list"); |
214 | 221 | } |
215 | 222 | try ( |
216 | | - InputStream in = manifest.openStream(); |
| 223 | + InputStream in = listingURL.openStream(); |
217 | 224 | InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8); |
218 | 225 | BufferedReader reader = new BufferedReader(isr) |
219 | 226 | ) { |
220 | 227 | List<String> jars = reader.lines().toList(); |
221 | | - Map<String, CodeSource> map = new HashMap<>(); |
| 228 | + Map<String, CodeSource> map = new LinkedHashMap<>(); |
222 | 229 | for (String jar : jars) { |
223 | | - map.put(providerPrefix + "/" + jar, new CodeSource(new URL(manifest, jar), (CodeSigner[]) null /*signers*/)); |
| 230 | + String jarPrefix = providerPrefix + "/" + jar; |
| 231 | + if (isMultiRelease(parent, jarPrefix)) { |
| 232 | + List<String> versionPrefixes = getVersionPrefixes(parent, jarPrefix); |
| 233 | + for (String versionPrefix : versionPrefixes) { |
| 234 | + map.put(versionPrefix, codeSource(listingURL, jar)); |
| 235 | + } |
| 236 | + } |
| 237 | + // TODO: LinkedHashMap, verify order, need to preserve order for versioned entries |
| 238 | + map.put(jarPrefix, codeSource(listingURL, jar)); |
224 | 239 | } |
225 | 240 | return Collections.unmodifiableMap(map); |
226 | 241 | } catch (IOException e) { |
227 | 242 | throw new UncheckedIOException(e); |
228 | 243 | } |
229 | 244 | } |
230 | 245 |
|
| 246 | + private static CodeSource codeSource(URL baseURL, String jarName) throws MalformedURLException { |
| 247 | + return new CodeSource(new URL(baseURL, jarName), (CodeSigner[]) null /*signers*/); |
| 248 | + } |
| 249 | + |
| 250 | + private static boolean isMultiRelease(ClassLoader parent, String jarPrefix) throws IOException { |
| 251 | + try (InputStream is = parent.getResourceAsStream(jarPrefix + "/META-INF/MANIFEST.MF")) { |
| 252 | + if (is != null) { |
| 253 | + Manifest manifest = new Manifest(is); |
| 254 | + return Boolean.parseBoolean(manifest.getMainAttributes().getValue(MULTI_RELEASE)); |
| 255 | + } |
| 256 | + } |
| 257 | + return false; |
| 258 | + } |
| 259 | + |
| 260 | + private static final int BASE_VERSION_FEATURE = 8; // lowest supported release version |
| 261 | + private static final int RUNTIME_VERSION_FEATURE = Runtime.version().feature(); |
| 262 | + |
| 263 | + static { |
| 264 | + assert RUNTIME_VERSION_FEATURE >= BASE_VERSION_FEATURE; |
| 265 | + } |
| 266 | + |
| 267 | + private static final String MRJAR_VERSION_PREFIX = "META-INF/versions/"; |
| 268 | + |
| 269 | + private static List<String> getVersionPrefixes(ClassLoader parent, String jarPrefix) throws IOException { |
| 270 | + List<String> versions = new ArrayList<>(); |
| 271 | + for (int v = RUNTIME_VERSION_FEATURE; v >= BASE_VERSION_FEATURE; v--) { |
| 272 | + URL url = parent.getResource(jarPrefix + "/" + MRJAR_VERSION_PREFIX + v); |
| 273 | + if (url != null) { |
| 274 | + versions.add(jarPrefix + "/" + MRJAR_VERSION_PREFIX + v); |
| 275 | + } |
| 276 | + } |
| 277 | + return versions; |
| 278 | + } |
| 279 | + |
231 | 280 | private static String getParent(String uriString) { |
232 | 281 | int index = uriString.lastIndexOf('/'); |
233 | 282 | if (index > 0) { |
|
0 commit comments