Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 44 additions & 50 deletions src/main/java/org/codehaus/plexus/resource/loader/JarHolder.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

Expand All @@ -47,72 +51,63 @@
public class JarHolder {
private final String urlpath;

private JarFile theJar = null;

private JarURLConnection conn = null;
private final Set<String> entries;

public JarHolder(String urlpath) {
this.urlpath = urlpath;

try {
URL url = new URL(urlpath);

conn = (JarURLConnection) url.openConnection();

conn.setAllowUserInteraction(false);

conn.setDoInput(true);

conn.setDoOutput(false);

conn.connect();
Set<String> names = Collections.emptySet();
// The jar is indexed once and every resource is read from a connection of its own, all without the
// JVM-wide jar: cache: a cached JarFile is never closed, so the jar stays locked on Windows and is read
// stale once it is rewritten (JDK-8239054).
try (JarFile jar = openJar(new URL(urlpath))) {
names = new LinkedHashSet<>();
Enumeration<JarEntry> all = jar.entries();
while (all.hasMoreElements()) {
JarEntry je = all.nextElement();

theJar = conn.getJarFile();
// We don't map plain directory entries
if (!je.isDirectory()) {
names.add(je.getName());
}
}
} catch (IOException ioe) {
}
this.entries = names;
}

public void close() {
try {
theJar.close();
} catch (Exception e) {
}

theJar = null;
private static JarFile openJar(URL url) throws IOException {
JarURLConnection conn = (JarURLConnection) url.openConnection();
conn.setUseCaches(false);
return conn.getJarFile();
}

conn = null;
private InputStream openEntry(String name) throws IOException {
URLConnection conn = new URL(urlpath + name).openConnection();
conn.setUseCaches(false);
return conn.getInputStream();
}

public InputStream getResource(String theentry) throws ResourceNotFoundException {
InputStream data = null;
/**
* Kept for compatibility: the jar is no longer held open, so there is nothing to close.
*/
public void close() {}

public InputStream getResource(String theentry) throws ResourceNotFoundException {
if (!entries.contains(theentry)) {
return null;
}
try {
JarEntry entry = theJar.getJarEntry(theentry);

if (entry != null) {
data = theJar.getInputStream(entry);
}
return openEntry(theentry);
} catch (Exception fnfe) {
throw new ResourceNotFoundException(fnfe.getMessage());
}

return data;
}

public Hashtable<String, String> getEntries() {
Hashtable<String, String> allEntries = new Hashtable<>(559);

if (theJar != null) {
Enumeration<JarEntry> all = theJar.entries();

while (all.hasMoreElements()) {
JarEntry je = all.nextElement();

// We don't map plain directory entries
if (!je.isDirectory()) {
allEntries.put(je.getName(), this.urlpath);
}
}
for (String name : entries) {
allEntries.put(name, this.urlpath);
}
return allEntries;
}
Expand All @@ -122,8 +117,7 @@ public String getUrlPath() {
}

public PlexusResource getPlexusResource(final String name) {
final JarEntry entry = theJar.getJarEntry(name);
if (entry == null) {
if (!entries.contains(name)) {
return null;
}
return new PlexusResource() {
Expand All @@ -134,12 +128,12 @@ public File getFile() {

@Override
public InputStream getInputStream() throws IOException {
return theJar.getInputStream(entry);
return openEntry(name);
}

@Override
public String getName() {
return conn.getURL() + name;
return urlpath + name;
}

@Override
Expand All @@ -149,7 +143,7 @@ public URI getURI() {

@Override
public URL getURL() throws IOException {
return new URL(conn.getJarFileURL(), name);
return new URL(urlpath + name);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.codehaus.plexus.resource.loader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

import org.codehaus.plexus.resource.PlexusResource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JarResourceLoaderTest {
@TempDir
Path tempDir;

@Test
void readsRewrittenJar() throws Exception {
Path jar = tempDir.resolve("resources.jar");
writeJar(jar, "first");
assertEquals("first", read(jar));

// Rewritten in place, as a reactor module or a reinstalled artifact is.
writeJar(jar, "second, and longer than the first");
assertEquals("second, and longer than the first", read(jar));

// The jar is not held open, so it can be deleted (fails on Windows while it is).
Files.delete(jar);
}

@Test
void urlPointsIntoJar() throws Exception {
Path jar = tempDir.resolve("resources.jar");
writeJar(jar, "content");
assertEquals(
"jar:" + jar.toUri() + "!/config/rules.txt",
resource(jar).getURL().toString());
}

private static PlexusResource resource(Path jar) throws Exception {
JarResourceLoader loader = new JarResourceLoader();
loader.addSearchPath("jar:" + jar.toUri());
return loader.getResource("config/rules.txt");
}

private static String read(Path jar) throws Exception {
PlexusResource resource = resource(jar);
try (InputStream in = resource.getInputStream()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
byte[] buffer = new byte[256];
for (int n; (n = in.read(buffer)) != -1; ) {
bytes.write(buffer, 0, n);
}
return new String(bytes.toByteArray(), StandardCharsets.UTF_8);
}
}

private static void writeJar(Path jar, String content) throws IOException {
try (OutputStream out = Files.newOutputStream(jar);
JarOutputStream jarOut = new JarOutputStream(out)) {
jarOut.putNextEntry(new ZipEntry("config/rules.txt"));
jarOut.write(content.getBytes(StandardCharsets.UTF_8));
jarOut.closeEntry();
}
}
}
Loading