Skip to content

Commit 88c3943

Browse files
committed
better classpath asset source implementation
1 parent 7bf0963 commit 88c3943

File tree

13 files changed

+186
-40
lines changed

13 files changed

+186
-40
lines changed

TODO

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
* tests and coverage
22
* server options from application.conf MUST override hardcoded settings
33
* raw responses, sendXXX should not allow decorator
4-
* BUG: AssetHandler should do sendError not sendStatus(404)
54

65
* make reset headers on error configurable
76

jooby/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,14 @@
269269
<scope>test</scope>
270270
</dependency>
271271

272+
<!-- asset from jar -->
273+
<dependency>
274+
<groupId>org.webjars.npm</groupId>
275+
<artifactId>vue</artifactId>
276+
<version>2.5.22</version>
277+
<scope>test</scope>
278+
</dependency>
279+
272280
</dependencies>
273281

274282
</project>

jooby/src/main/java/io/jooby/AssetSource.java

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.internal.ClasspathAssetSource;
89
import io.jooby.internal.FileDiskAssetSource;
910
import io.jooby.internal.FolderDiskAssetSource;
1011

1112
import javax.annotation.Nonnull;
1213
import javax.annotation.Nullable;
1314
import java.io.FileNotFoundException;
14-
import java.net.URL;
1515
import java.nio.file.Files;
1616
import java.nio.file.Path;
1717

@@ -34,35 +34,15 @@ public interface AssetSource {
3434
@Nullable Asset resolve(@Nonnull String path);
3535

3636
/**
37-
* Classpath/url-based asset source. Useful for resolving files from classpath
37+
* Classpath asset source. Useful for resolving files from classpath
3838
* (including jar files).
3939
*
4040
* @param loader Class loader.
4141
* @param location Classpath location.
4242
* @return An asset source.
4343
*/
4444
static @Nonnull AssetSource create(@Nonnull ClassLoader loader, @Nonnull String location) {
45-
String safeloc = Router.normalizePath(location, false, true)
46-
.substring(1);
47-
MediaType type = MediaType.byFile(location);
48-
if (type != MediaType.octetStream) {
49-
URL resource = loader
50-
.getResource(location.startsWith("/") ? location.substring(1) : location);
51-
if (resource != null) {
52-
return path -> Asset.create(location, resource);
53-
}
54-
}
55-
String prefix = safeloc + (safeloc.length() > 0 ? "/" : "");
56-
return path -> {
57-
String[] paths = {prefix + path + "/index.html", prefix + path};
58-
for (String it : paths) {
59-
URL resource = loader.getResource(it);
60-
if (resource != null) {
61-
return Asset.create(it, resource);
62-
}
63-
}
64-
return null;
65-
};
45+
return new ClasspathAssetSource(loader, location);
6646
}
6747

6848
/**
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.jooby.internal;
2+
3+
import io.jooby.Asset;
4+
import io.jooby.AssetSource;
5+
import io.jooby.SneakyThrows;
6+
7+
import javax.annotation.Nonnull;
8+
import javax.annotation.Nullable;
9+
import java.io.IOException;
10+
import java.net.JarURLConnection;
11+
import java.net.URL;
12+
import java.net.URLConnection;
13+
import java.nio.file.Files;
14+
import java.nio.file.Paths;
15+
import java.util.jar.JarFile;
16+
import java.util.zip.ZipEntry;
17+
18+
public class ClasspathAssetSource implements AssetSource {
19+
20+
private final ClassLoader loader;
21+
22+
private final String source;
23+
24+
private final boolean isDir;
25+
26+
private final String prefix;
27+
28+
public ClasspathAssetSource(ClassLoader loader, String source) {
29+
this.loader = loader;
30+
this.source = source.startsWith("/") ? source.substring(1) : source;
31+
this.prefix = sourcePrefix(this.source);
32+
isDir = isDirectory(loader, this.source);
33+
}
34+
35+
@Nullable @Override public Asset resolve(@Nonnull String path) {
36+
String fullpath = isDir ? prefix + path : source;
37+
URL resource = loader.getResource(fullpath);
38+
if (resource == null) {
39+
return null;
40+
}
41+
Asset asset = Asset.create(fullpath, resource);
42+
if (asset.getSize() > 0) {
43+
return asset;
44+
}
45+
return null;
46+
}
47+
48+
private String sourcePrefix(String path) {
49+
if (path.length() > 0 && !path.endsWith("/")) {
50+
return path + "/";
51+
}
52+
return path;
53+
}
54+
55+
private boolean isDirectory(ClassLoader loader, String base) {
56+
try {
57+
URL url = loader.getResource(base);
58+
if (url == null) {
59+
return true;
60+
}
61+
URLConnection connection = url.openConnection();
62+
if (connection instanceof JarURLConnection) {
63+
JarURLConnection jarConnection = (JarURLConnection) connection;
64+
try (JarFile jar = jarConnection.getJarFile()) {
65+
ZipEntry entry = jar.getEntry(base);
66+
return entry.isDirectory();
67+
}
68+
}
69+
return Files.isDirectory(Paths.get(url.toURI()));
70+
} catch (Exception x) {
71+
return true;
72+
}
73+
}
74+
}

jooby/src/main/java/io/jooby/internal/RouterImpl.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -576,21 +576,20 @@ private void find(String prefix, Class type, SneakyThrows.Consumer<MvcMethod> co
576576
MvcMetadata mvcMetadata = new MvcMetadata(source);
577577
mvcMetadata.parse(type);
578578
List<MvcMethod> routes = new ArrayList<>();
579-
Stream.of(type.getDeclaredMethods())
580-
.forEach(method -> {
581-
if (Modifier.isPublic(method.getModifiers())) {
582-
annotationParser.parse(method).forEach(model -> {
583-
String[] paths = pathPrefix(prefix, model.getPath());
584-
for (String path : paths) {
585-
MvcMethod mvc = mvcMetadata.create(method);
586-
mvc.setPattern(path);
587-
mvc.setModel(model);
588-
mvc.setMethod(method);
589-
routes.add(mvc);
590-
}
591-
});
579+
Stream.of(type.getDeclaredMethods()).forEach(method -> {
580+
if (Modifier.isPublic(method.getModifiers())) {
581+
annotationParser.parse(method).forEach(model -> {
582+
String[] paths = pathPrefix(prefix, model.getPath());
583+
for (String path : paths) {
584+
MvcMethod mvc = mvcMetadata.create(method);
585+
mvc.setPattern(path);
586+
mvc.setModel(model);
587+
mvc.setMethod(method);
588+
routes.add(mvc);
592589
}
593590
});
591+
}
592+
});
594593
Collections.sort(routes, Comparator.comparingInt(MvcMethod::getLine));
595594
routes.forEach(mvc -> {
596595
String executorKey = dispatchTo(mvc.getMethod());
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.jooby.internal;
2+
3+
import io.jooby.Asset;
4+
import io.jooby.AssetSource;
5+
import io.jooby.MediaType;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.util.function.Consumer;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
import static org.junit.jupiter.api.Assertions.assertNull;
13+
14+
public class ClasspathAssetSourceTest {
15+
16+
@Test
17+
public void checkclasspathFiles() {
18+
assetSource("/META-INF/resources/webjars/vue/2.5.22", source -> {
19+
Asset vuejs = source.resolve("dist/vue.js");
20+
assertNotNull(vuejs);
21+
assertEquals(MediaType.js, vuejs.getContentType());
22+
23+
Asset packagejson = source.resolve("package.json");
24+
assertNotNull(packagejson);
25+
assertEquals(MediaType.json, packagejson.getContentType());
26+
27+
Asset root = source.resolve("");
28+
assertNull(root);
29+
});
30+
31+
assetSource("/META-INF/resources/webjars/vue/2.5.22/dist", source -> {
32+
Asset vuejs = source.resolve("vue.js");
33+
assertNotNull(vuejs);
34+
assertEquals(MediaType.js, vuejs.getContentType());
35+
36+
Asset root = source.resolve("");
37+
assertNull(root);
38+
});
39+
40+
assetSource("/META-INF/resources/webjars/vue/2.5.22/dist/vue.js", source -> {
41+
Asset vuejs = source.resolve("vue.js");
42+
assertNotNull(vuejs);
43+
assertEquals(MediaType.js, vuejs.getContentType());
44+
});
45+
46+
assetSource("/", source -> {
47+
Asset logback = source.resolve("logback.xml");
48+
assertNotNull(logback);
49+
assertEquals(MediaType.xml, logback.getContentType());
50+
});
51+
}
52+
53+
private void assetSource(String location, Consumer<AssetSource> consumer) {
54+
AssetSource source = new ClasspathAssetSource(getClass().getClassLoader(), location);
55+
consumer.accept(source);
56+
}
57+
}

modules/jooby-hikari/src/main/java/io/jooby/hikari/HikariModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public HikariModule(@Nonnull HikariConfig hikari) {
230230
String key = url.substring(pnameStart, pnameEnd).trim();
231231
if (key.equalsIgnoreCase("databaseName") || key.equalsIgnoreCase("database")) {
232232
dbname = url.substring(pnameEnd + 1, i).trim();
233-
233+
break;
234234
}
235235
pnameStart = i + 1;
236236
} else if (ch == '=') {

modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ private Context sendStreamInternal(@Nonnull InputStream in) {
426426
return InvocationType.NON_BLOCKING;
427427
}
428428

429+
@Override public String toString() {
430+
return getMethod() + " " + pathString();
431+
}
432+
429433
void complete(Throwable x) {
430434
ifSaveSession();
431435

modules/jooby-maven-plugin/src/test/java/io/jooby/maven/RunMojoTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.junit.jupiter.api.Test;
55

66
import java.lang.reflect.Field;
7+
import java.lang.reflect.Modifier;
78
import java.util.stream.Stream;
89

910
import static junit.framework.Assert.assertEquals;
@@ -13,7 +14,7 @@ public class RunMojoTest {
1314
@Test
1415
public void ensureConfigurationOptions() {
1516
Stream.of(JoobyRunOptions.class.getDeclaredFields())
16-
.filter(field -> !field.getName().equals("projectName"))
17+
.filter(field -> !field.getName().equals("projectName") && !Modifier.isStatic(field.getModifiers()))
1718
.forEach(field -> {
1819
try {
1920
Field target = RunMojo.class.getDeclaredField(field.getName());

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,4 +577,8 @@ private void prepareChunked() {
577577
setHeaders.set(TRANSFER_ENCODING, CHUNKED);
578578
}
579579
}
580+
581+
@Override public String toString() {
582+
return getMethod() + " " + pathString();
583+
}
580584
}

0 commit comments

Comments
 (0)