From 20c450400e18bfe7f44c00a6872126e992d7d756 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sat, 7 Dec 2024 05:08:40 +1300 Subject: [PATCH] Rename StaticContent and add Builder interface (#134) --- .../jex/staticcontent/StaticContent.java | 127 ++++++++++++++++++ .../staticcontent/StaticContentService.java | 100 -------------- .../StaticResourceHandlerBuilder.java | 7 +- .../avaje/jex/staticcontent/package-info.java | 2 +- .../src/main/java/module-info.java | 2 +- .../CompressedStaticFileTest.java | 28 ++-- .../jex/staticcontent/StaticFileTest.java | 26 ++-- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 1 + 8 files changed, 164 insertions(+), 129 deletions(-) create mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java delete mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java new file mode 100644 index 0000000..d3baf45 --- /dev/null +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java @@ -0,0 +1,127 @@ +package io.avaje.jex.staticcontent; + +import java.net.URLConnection; +import java.util.function.Predicate; + +import io.avaje.jex.Context; +import io.avaje.jex.spi.JexPlugin; + +/** + * Static content resource handler. + *
{@code
+ *
+ *  var staticContent = StaticContent.createFile("src/test/resources/public")
+ *     .directoryIndex("index.html")
+ *     .preCompress()
+ *     .build()
+ *
+ *  Jex.create()
+ *    .plugin(staticContent)
+ *    .port(8080)
+ *    .start();
+ *
+ * }
+ */ +public sealed interface StaticContent extends JexPlugin + permits StaticResourceHandlerBuilder { + + /** + * Create and return a new static content class path configuration. + * + * @param resourceRoot The file to serve, or the directory the files are located in. + */ + static Builder createCP(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot); + } + + /** + * Create and return a new static content class path configuration with the + * `/public` directory as the root. + */ + static Builder createCP() { + return StaticResourceHandlerBuilder.builder("/public/"); + } + + /** + * Create and return a new static content configuration for a File. + * + * @param resourceRoot The path of the file to serve, or the directory the files are located in. + */ + static Builder createFile(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot).file(); + } + + /** + * Builder for StaticContent. + */ + sealed interface Builder + permits StaticResourceHandlerBuilder { + + /** + * Sets the HTTP path for the static resource handler. + * + * @param path the HTTP path prefix + * @return the updated configuration + */ + Builder httpPath(String path); + + /** + * Sets the index file to be served when a directory is requests. + * + * @param directoryIndex the index file + * @return the updated configuration + */ + Builder directoryIndex(String directoryIndex); + + /** + * Sent resources will be pre-compressed and cached in memory when this is enabled + * + * @return the updated configuration + */ + Builder preCompress(); + + /** + * Sets a custom resource loader for loading class/module path resources. This is normally used + * when running the application on the module path when files cannot be discovered. + * + *

Example usage: {@code service.resourceLoader(ClassResourceLoader.create(getClass())) } + * + * @param resourceLoader the custom resource loader + * @return the updated configuration + */ + Builder resourceLoader(ClassResourceLoader resourceLoader); + + /** + * Adds a new MIME type mapping to the configuration. (Default: uses {@link + * URLConnection#getFileNameMap()} + * + * @param ext the file extension (e.g., "html", "css", "js") + * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", + * "application/javascript") + * @return the updated configuration + */ + Builder putMimeTypeMapping(String ext, String mimeType); + + /** + * Adds a new response header to the configuration. + * + * @param key the header name + * @param value the header value + * @return the updated configuration + */ + Builder putResponseHeader(String key, String value); + + /** + * Sets a predicate to filter files based on the request context. + * + * @param skipFilePredicate the predicate to use + * @return the updated configuration + */ + Builder skipFilePredicate(Predicate skipFilePredicate); + + /** + * Build and return the StaticContent. + */ + StaticContent build(); + } +} diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java deleted file mode 100644 index 0008588..0000000 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.avaje.jex.staticcontent; - -import java.net.URLConnection; -import java.util.function.Predicate; - -import io.avaje.jex.Context; -import io.avaje.jex.spi.JexPlugin; - -/** Builder for a static resource exchange handler. */ -public sealed interface StaticContentService extends JexPlugin - permits StaticResourceHandlerBuilder { - - /** - * Create and return a new static class path content configuration. - * - * @param resourceRoot The file to serve, or the directory the files are located in. - */ - static StaticContentService createCP(String resourceRoot) { - return StaticResourceHandlerBuilder.builder(resourceRoot); - } - - /** - * Create and return a new static class path content configuration with the `/public` directory as - * the root. - */ - static StaticContentService createCP() { - return StaticResourceHandlerBuilder.builder("/public/"); - } - - /** - * Create and return a new static content configuration for a File. - * - * @param resourceRoot The path of the file to serve, or the directory the files are located in. - */ - static StaticContentService createFile(String resourceRoot) { - return StaticResourceHandlerBuilder.builder(resourceRoot).file(); - } - - /** - * Sets the HTTP path for the static resource handler. - * - * @param path the HTTP path prefix - * @return the updated configuration - */ - StaticContentService httpPath(String path); - - /** - * Sets the index file to be served when a directory is requests. - * - * @param directoryIndex the index file - * @return the updated configuration - */ - StaticContentService directoryIndex(String directoryIndex); - - /** - * Sent resources will be pre-compressed and cached in memory when this is enabled - * - * @return the updated configuration - */ - StaticContentService preCompress(); - - /** - * Sets a custom resource loader for loading class/module path resources. This is normally used - * when running the application on the module path when files cannot be discovered. - * - *

Example usage: {@code service.resourceLoader(ClassResourceLoader.create(getClass())) } - * - * @param resourceLoader the custom resource loader - * @return the updated configuration - */ - StaticContentService resourceLoader(ClassResourceLoader resourceLoader); - - /** - * Adds a new MIME type mapping to the configuration. (Default: uses {@link - * URLConnection#getFileNameMap()} - * - * @param ext the file extension (e.g., "html", "css", "js") - * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", - * "application/javascript") - * @return the updated configuration - */ - StaticContentService putMimeTypeMapping(String ext, String mimeType); - - /** - * Adds a new response header to the configuration. - * - * @param key the header name - * @param value the header value - * @return the updated configuration - */ - StaticContentService putResponseHeader(String key, String value); - - /** - * Sets a predicate to filter files based on the request context. - * - * @param skipFilePredicate the predicate to use - * @return the updated configuration - */ - StaticContentService skipFilePredicate(Predicate skipFilePredicate); -} diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 3ad1323..2546758 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -12,7 +12,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.compression.CompressionConfig; -final class StaticResourceHandlerBuilder implements StaticContentService { +final class StaticResourceHandlerBuilder implements StaticContent.Builder, StaticContent { private static final String FAILED_TO_LOCATE_FILE = "Failed to locate file: "; private static final String DIRECTORY_INDEX_FAILURE = @@ -43,6 +43,11 @@ public void apply(Jex jex) { jex.get(path, createHandler(jex.config().compression())); } + @Override + public StaticContent build() { + return this; + } + ExchangeHandler createHandler(CompressionConfig compress) { path = Objects.requireNonNull(path) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java index 0aa9f70..16ad428 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java @@ -1,5 +1,5 @@ /** - * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContentService}. + * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContent}. * *

{@code
  * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java
index c2ff0de..e50bd8e 100644
--- a/avaje-jex-static-content/src/main/java/module-info.java
+++ b/avaje-jex-static-content/src/main/java/module-info.java
@@ -1,5 +1,5 @@
 /**
- * Defines the Static Content API for serving static resources with Jex - see {@link io.avaje.jex.staticcontent.StaticContentService}.
+ * Defines the Static Content API for serving static resources with Jex - see {@link StaticContent}.
  *
  * 
{@code
  * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
index 15a4b26..86ab80b 100644
--- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
+++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
@@ -19,28 +19,30 @@ static TestPair init() {
 
     final Jex app =
         Jex.create()
-            .plugin(defaultCP().httpPath("/index"))
-            .plugin(defaultFile().httpPath("/indexFile"))
-            .plugin(defaultCP().httpPath("/indexWild/*"))
-            .plugin(defaultFile().httpPath("/indexWildFile/*"))
-            .plugin(defaultCP().httpPath("/sus/"))
-            .plugin(defaultFile().httpPath("/susFile/*"))
-            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
+            .plugin(defaultCP().httpPath("/index").build())
+            .plugin(defaultFile().httpPath("/indexFile").build())
+            .plugin(defaultCP().httpPath("/indexWild/*").build())
+            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
+            .plugin(defaultCP().httpPath("/sus/").build())
+            .plugin(defaultFile().httpPath("/susFile/*").build())
+            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
             .plugin(
-                StaticContentService.createFile("src/test/resources/logback.xml")
-                    .httpPath("/singleFile"));
+                StaticContent.createFile("src/test/resources/logback.xml")
+                    .httpPath("/singleFile").build());
 
     return TestPair.create(app);
   }
 
-  private static StaticContentService defaultFile() {
-    return StaticContentService.createFile("src/test/resources/public")
+  private static StaticContent.Builder defaultFile() {
+    return StaticContent.createFile("src/test/resources/public")
         .directoryIndex("index.html")
         .preCompress();
   }
 
-  private static StaticContentService defaultCP() {
-    return StaticContentService.createCP("/public").directoryIndex("index.html").preCompress();
+  private static StaticContent.Builder defaultCP() {
+    return StaticContent.createCP("/public")
+      .directoryIndex("index.html")
+      .preCompress();
   }
 
   @AfterAll
diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
index 2f1cc8b..f7290eb 100644
--- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
+++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
@@ -19,27 +19,27 @@ static TestPair init() {
 
     final Jex app =
         Jex.create()
-            .plugin(defaultCP().httpPath("/index"))
-            .plugin(defaultFile().httpPath("/indexFile"))
-            .plugin(defaultCP().httpPath("/indexWild/*"))
-            .plugin(defaultFile().httpPath("/indexWildFile/*"))
-            .plugin(defaultCP().httpPath("/sus/"))
-            .plugin(defaultFile().httpPath("/susFile/*"))
-            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
+            .plugin(defaultCP().httpPath("/index").build())
+            .plugin(defaultFile().httpPath("/indexFile").build())
+            .plugin(defaultCP().httpPath("/indexWild/*").build())
+            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
+            .plugin(defaultCP().httpPath("/sus/").build())
+            .plugin(defaultFile().httpPath("/susFile/*").build())
+            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
             .plugin(
-                StaticContentService.createFile("src/test/resources/logback.xml")
-                    .httpPath("/singleFile"));
+                StaticContent.createFile("src/test/resources/logback.xml")
+                    .httpPath("/singleFile").build());
 
     return TestPair.create(app);
   }
 
-  private static StaticContentService defaultFile() {
-    return StaticContentService.createFile("src/test/resources/public")
+  private static StaticContent.Builder defaultFile() {
+    return StaticContent.createFile("src/test/resources/public")
         .directoryIndex("index.html");
   }
 
-  private static StaticContentService defaultCP() {
-    return StaticContentService.createCP("/public").directoryIndex("index.html");
+  private static StaticContent.Builder defaultCP() {
+    return StaticContent.createCP("/public").directoryIndex("index.html");
   }
 
   @AfterAll
diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
index af0afb6..d768e0d 100644
--- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java
+++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
@@ -30,6 +30,7 @@ public sealed interface Jex permits DJex {
    * Create Jex.
    *
    * 
{@code
+   *
    * final Jex.Server app = Jex.create()
    *   .routing(routing -> routing
    *     .get("/", ctx -> ctx.text("hello world"))