Skip to content

Commit c41d89c

Browse files
committed
Add a new class that can extract files from a classloader classpath (jar or folder).
1 parent d23b1f9 commit c41d89c

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package edu.hm.hafner.util;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.io.OutputStream;
7+
import java.io.UncheckedIOException;
8+
import java.net.URL;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.StandardCopyOption;
12+
import java.security.CodeSource;
13+
import java.util.Arrays;
14+
import java.util.Enumeration;
15+
import java.util.Set;
16+
import java.util.jar.JarEntry;
17+
import java.util.jar.JarFile;
18+
import java.util.stream.Collectors;
19+
20+
import org.apache.commons.io.IOUtils;
21+
import org.apache.commons.lang3.StringUtils;
22+
23+
/**
24+
* A proxy for resources. Extracts a given collection of files from the classpath and copies them to a target path.
25+
*
26+
* @author Ullrich Hafner
27+
*/
28+
public class ResourceExtractor {
29+
private final boolean readingFromJarFile;
30+
private final Extractor extractor;
31+
32+
/**
33+
* Creates a new {@link ResourceExtractor} that extracts resources from the classloader of the specified class.
34+
*
35+
* @param targetClass
36+
* the target class to use the classloader from
37+
*/
38+
public ResourceExtractor(final Class<?> targetClass) {
39+
CodeSource codeSource = targetClass.getProtectionDomain().getCodeSource();
40+
if (codeSource == null) {
41+
throw new IllegalArgumentException("There is no CodeSource for " + targetClass);
42+
}
43+
URL location = codeSource.getLocation();
44+
if (location == null) {
45+
throw new IllegalArgumentException("There is no CodeSource location for " + targetClass);
46+
}
47+
String locationPath = location.getPath();
48+
if (StringUtils.isBlank(locationPath)) {
49+
throw new IllegalArgumentException("The CodeSource location path is not set for " + targetClass);
50+
}
51+
Path entryPoint = new File(locationPath).toPath();
52+
readingFromJarFile = Files.isRegularFile(entryPoint);
53+
if (readingFromJarFile) {
54+
extractor = new JarExtractor(entryPoint);
55+
}
56+
else {
57+
extractor = new FolderExtractor(entryPoint);
58+
}
59+
}
60+
61+
public boolean isReadingFromJarFile() {
62+
return readingFromJarFile;
63+
}
64+
65+
/**
66+
* Extracts the specified source files from the classloader and saves them to the specified target folder.
67+
*
68+
* @param targetDirectory
69+
* the target path that will be the parent folder of all extracted files
70+
* @param source
71+
* the source file to extract
72+
* @param sources
73+
* the additional source files to extract
74+
*/
75+
public void extract(final Path targetDirectory, final String source, final String... sources) {
76+
if (!Files.isDirectory(targetDirectory)) {
77+
throw new IllegalArgumentException(
78+
"Target directory must be an existing directory: " + targetDirectory); // implement
79+
}
80+
String[] allSources = Arrays.copyOf(sources, sources.length + 1);
81+
allSources[sources.length] = source;
82+
extractor.extractFiles(targetDirectory, allSources);
83+
}
84+
85+
/**
86+
* Extracts a collection of files and copies them to a given target path.
87+
*/
88+
private abstract static class Extractor {
89+
private final Path entryPoint;
90+
91+
Extractor(final Path entryPoint) {
92+
this.entryPoint = entryPoint;
93+
}
94+
95+
Path getEntryPoint() {
96+
return entryPoint;
97+
}
98+
99+
abstract void extractFiles(Path targetDirectory, String... sources);
100+
}
101+
102+
/**
103+
* Extracts files from a folder, typically provided by the development environment or build system.
104+
*/
105+
private static class FolderExtractor extends Extractor {
106+
FolderExtractor(final Path entryPoint) {
107+
super(entryPoint);
108+
}
109+
110+
@Override
111+
public void extractFiles(final Path targetDirectory, final String... sources) {
112+
try {
113+
for (String source : sources) {
114+
Path targetFile = targetDirectory.resolve(source);
115+
Files.createDirectories(targetFile);
116+
copy(targetFile, source);
117+
}
118+
}
119+
catch (IOException exception) {
120+
throw new UncheckedIOException(exception);
121+
}
122+
}
123+
124+
private void copy(final Path target, final String source) {
125+
try {
126+
Files.copy(getEntryPoint().resolve(source), target, StandardCopyOption.REPLACE_EXISTING);
127+
}
128+
catch (IOException exception) {
129+
throw new UncheckedIOException(exception);
130+
}
131+
}
132+
}
133+
134+
/**
135+
* Extracts files from a deployed jar file.
136+
*/
137+
private static class JarExtractor extends Extractor {
138+
JarExtractor(final Path entryPoint) {
139+
super(entryPoint);
140+
}
141+
142+
@Override
143+
public void extractFiles(final Path targetDirectory, final String... sources) {
144+
Set<String> remaining = Arrays.stream(sources).collect(Collectors.toSet());
145+
try (JarFile jar = new JarFile(getEntryPoint().toFile())) {
146+
Enumeration<JarEntry> entries = jar.entries();
147+
while (entries.hasMoreElements()) {
148+
JarEntry entry = entries.nextElement();
149+
String name = entry.getName();
150+
if (remaining.contains(name)) {
151+
Path targetFile = targetDirectory.resolve(name);
152+
Files.createDirectories(targetFile.getParent());
153+
try (InputStream inputStream = jar.getInputStream(entry); OutputStream outputStream = Files.newOutputStream(targetFile)) {
154+
IOUtils.copy(inputStream, outputStream);
155+
}
156+
remaining.remove(name);
157+
}
158+
}
159+
}
160+
catch (IOException exception) {
161+
throw new UncheckedIOException(exception);
162+
}
163+
if (!remaining.isEmpty()) {
164+
throw new NoSuchElementException("The following files have not been found: " + remaining);
165+
}
166+
}
167+
}
168+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package edu.hm.hafner.util;
2+
3+
import java.io.IOException;
4+
import java.io.UncheckedIOException;
5+
import java.nio.charset.StandardCharsets;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
9+
import org.apache.commons.lang3.StringUtils;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.io.TempDir;
12+
13+
import static org.assertj.core.api.Assertions.*;
14+
15+
/**
16+
* Tests the class {@link ResourceExtractor}.
17+
*
18+
* @author Ullrich Hafner
19+
*/
20+
class ResourceExtractorTest {
21+
private static final String ASSERTJ_TEMPLATES = "assertj-templates/has_assertion_template.txt";
22+
private static final String JENKINS_FILE = "Jenkinsfile.reference";
23+
private static final String MANIFEST_MF = "META-INF/MANIFEST.MF";
24+
25+
@Test
26+
void shouldLocateResourcesInJarFilesAndClassPath() {
27+
assertThat(new ResourceExtractor(ResourceExtractor.class).isReadingFromJarFile()).isFalse();
28+
assertThat(new ResourceExtractor(StringUtils.class).isReadingFromJarFile()).isTrue();
29+
}
30+
31+
@Test
32+
void shouldExtractFromFolder(@TempDir final Path targetFolder) {
33+
ResourceExtractor proxy = new ResourceExtractor(ResourceExtractor.class);
34+
35+
proxy.extract(targetFolder, ASSERTJ_TEMPLATES, JENKINS_FILE,
36+
"edu/hm/hafner/util/ResourceExtractor.class");
37+
38+
assertThat(readToString(targetFolder.resolve(ASSERTJ_TEMPLATES)))
39+
.contains("has${Property}(${propertyType} ${property_safe})");
40+
assertThat(readToString(targetFolder.resolve(JENKINS_FILE)))
41+
.contains("node", "stage ('Build and Static Analysis')");
42+
}
43+
44+
@Test
45+
void shouldThrowExceptionIfTargetIsFileInFolder() throws IOException {
46+
ResourceExtractor proxy = new ResourceExtractor(ResourceExtractor.class);
47+
48+
Path tempFile = Files.createTempFile("tmp", "tmp");
49+
assertThatIllegalArgumentException().isThrownBy(() -> proxy.extract(tempFile, MANIFEST_MF));
50+
}
51+
52+
@Test
53+
void shouldThrowExceptionIfFileDoesNotExistInFolder(@TempDir final Path targetFolder) {
54+
ResourceExtractor proxy = new ResourceExtractor(ResourceExtractor.class);
55+
56+
assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() ->
57+
proxy.extract(targetFolder, "does-not-exist"));
58+
}
59+
60+
@Test
61+
void shouldExtractFromJar(@TempDir final Path targetFolder) {
62+
ResourceExtractor proxy = new ResourceExtractor(StringUtils.class);
63+
64+
proxy.extract(targetFolder, MANIFEST_MF,
65+
"org/apache/commons/lang3/StringUtils.class");
66+
67+
assertThat(readToString(targetFolder.resolve(MANIFEST_MF))).contains("Manifest-Version: 1.0",
68+
"Bundle-SymbolicName: org.apache.commons.lang3");
69+
}
70+
71+
@Test
72+
void shouldThrowExceptionIfTargetIsFileInJar() throws IOException {
73+
ResourceExtractor proxy = new ResourceExtractor(StringUtils.class);
74+
75+
Path tempFile = Files.createTempFile("tmp", "tmp");
76+
assertThatIllegalArgumentException().isThrownBy(() -> proxy.extract(tempFile, MANIFEST_MF));
77+
}
78+
79+
@Test
80+
void shouldThrowExceptionIfFileDoesNotExistInJar(@TempDir final Path targetFolder) {
81+
ResourceExtractor proxy = new ResourceExtractor(StringUtils.class);
82+
83+
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() ->
84+
proxy.extract(targetFolder, "does-not-exist"));
85+
}
86+
87+
private String readToString(final Path output) {
88+
try {
89+
return new String(Files.readAllBytes(output), StandardCharsets.UTF_8);
90+
}
91+
catch (IOException exception) {
92+
throw new UncheckedIOException(exception);
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)