From 707400a8b2c26858cdb784ecd8c14e1113d06322 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sat, 14 Jun 2025 16:07:51 -0500 Subject: [PATCH 1/4] Added support for loading static files from the filesystem as as from jars (based on a system property) --- qqq-middleware-javalin/pom.xml | 12 ++++ .../SimpleFileSystemDirectoryRouter.java | 68 ++++++++++++++----- ...ApplicationJavalinServerClasspathTest.java | 61 +++++++++++++++++ .../QApplicationJavalinServerTest.java | 40 ++++++++--- 4 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java diff --git a/qqq-middleware-javalin/pom.xml b/qqq-middleware-javalin/pom.xml index dc94f1ae..43ff6aef 100644 --- a/qqq-middleware-javalin/pom.xml +++ b/qqq-middleware-javalin/pom.xml @@ -122,6 +122,18 @@ src/main/java + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + + + + ${project.basedir}/src/test/resources/static-site.jar + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java index c6504be1..89e424e2 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java @@ -49,16 +49,34 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInterface { private static final QLogger LOG = QLogger.getLogger(SimpleFileSystemDirectoryRouter.class); + public static boolean loadStaticFilesFromJar = false; + + static + { + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // read the property to see if we should load static files from the jar file or from the file system // + // Javan only supports loading via one method per path, so its a choice of one or the other... // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + try + { + String propertyName = "qqq.javalin.enableStaticFilesFromJar"; // TODO: make a more general way to handle properties like this system-wide via a central config class + String propertyValue = System.getProperty(propertyName, ""); + if(propertyValue.equals("true")) + { + loadStaticFilesFromJar = true; + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } private final String hostedPath; private final String fileSystemPath; - private QCodeReference routeAuthenticator; - private QInstance qInstance; - - /******************************************************************************* ** Constructor ** @@ -98,25 +116,41 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt ***************************************************************************/ private void handleJavalinStaticFileConfig(StaticFileConfig staticFileConfig) { - URL resource = getClass().getClassLoader().getResource(fileSystemPath); - if(resource == null) - { - String message = "Could not find file system path: " + fileSystemPath; - if(fileSystemPath.startsWith("/") && getClass().getClassLoader().getResource(fileSystemPath.replaceFirst("^/+", "")) != null) - { - message += ". For non-absolute paths, do not prefix with a leading slash."; - } - throw new RuntimeException(message); - } if(!hostedPath.startsWith("/")) { LOG.warn("hostedPath [" + hostedPath + "] should probably start with a leading slash..."); } - staticFileConfig.directory = resource.getFile(); - staticFileConfig.hostedPath = hostedPath; - staticFileConfig.location = Location.EXTERNAL; + /// ///////////////////////////////////////////////////////////////////////////////////// + // Handle loading static files from the jar OR the filesystem based on system property // + /// ///////////////////////////////////////////////////////////////////////////////////// + if(SimpleFileSystemDirectoryRouter.loadStaticFilesFromJar) + { + staticFileConfig.directory = fileSystemPath; + staticFileConfig.hostedPath = hostedPath; + staticFileConfig.location = Location.CLASSPATH; + LOG.info("Static File Config : hostedPath [" + hostedPath + "] : directory [" + staticFileConfig.directory + "] : location [CLASSPATH]"); + } + else + { + URL resource = getClass().getClassLoader().getResource(fileSystemPath); + if(resource == null) + { + String message = "Could not find file system path: " + fileSystemPath; + if(fileSystemPath.startsWith("/") && getClass().getClassLoader().getResource(fileSystemPath.replaceFirst("^/+", "")) != null) + { + message += ". For non-absolute paths, do not prefix with a leading slash."; + } + throw new RuntimeException(message); + } + + staticFileConfig.directory = resource.getFile(); + staticFileConfig.hostedPath = hostedPath; + staticFileConfig.location = Location.EXTERNAL; + LOG.info("Static File Config : hostedPath [" + hostedPath + "] : directory [" + staticFileConfig.directory + "] : location [EXTERNAL]"); + } + } diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java new file mode 100644 index 00000000..f75b99d1 --- /dev/null +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java @@ -0,0 +1,61 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.middleware.javalin; + + +import com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/******************************************************************************* + ** Unit test for QApplicationJavalinServer with static files served from classpath + *******************************************************************************/ +class QApplicationJavalinServerClasspathTest +{ + private static final int PORT = 6265; + private QApplicationJavalinServer javalinServer; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testStaticRouterFilesFromClasspath() throws Exception + { + System.setProperty("qqq.javalin.enableStaticFilesFromJar", "true"); + + javalinServer = new QApplicationJavalinServer(new QApplicationJavalinServerTest.TestApplication()) + .withServeFrontendMaterialDashboard(false) + .withPort(PORT) + .withAdditionalRouteProvider(new SimpleFileSystemDirectoryRouter("/statically-served-from-jar", "static-site-from-jar/")); + + javalinServer.start(); + + Unirest.config().setDefaultResponseEncoding("UTF-8"); + HttpResponse response = Unirest.get("http://localhost:" + PORT + "/statically-served-from-jar/foo-in-jar.html").asString(); + assertEquals("Foo in a Jar!\n", response.getBody()); + } +} \ No newline at end of file diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java index a6bf4085..1d3c8e72 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java @@ -52,6 +52,16 @@ class QApplicationJavalinServerTest + /*************************************************************************** + ** + ***************************************************************************/ + private static AbstractQQQApplication getQqqApplication() + { + return new TestApplication(); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -196,6 +206,26 @@ class QApplicationJavalinServerTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testStaticRouterFilesFromExternal() throws Exception + { + System.setProperty("qqq.javalin.enableStaticFilesFromJar", "false"); + + javalinServer = new QApplicationJavalinServer(getQqqApplication()) + .withServeFrontendMaterialDashboard(false) + .withPort(PORT); + javalinServer.start(); + + Unirest.config().setDefaultResponseEncoding("UTF-8"); + HttpResponse response = Unirest.get("http://localhost:" + PORT + "/statically-served/foo.html").asString(); + assertEquals("Foo? Bar!", response.getBody()); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -296,16 +326,6 @@ class QApplicationJavalinServerTest - /*************************************************************************** - ** - ***************************************************************************/ - private static AbstractQQQApplication getQqqApplication() - { - return new TestApplication(); - } - - - /*************************************************************************** ** ***************************************************************************/ From eab87b9d807f0693a534e259367556ea871e0162 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 15 Jun 2025 10:01:11 -0500 Subject: [PATCH 2/4] Added missing jar for unit test --- .../src/test/resources/static-site.jar | Bin 0 -> 621 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qqq-middleware-javalin/src/test/resources/static-site.jar diff --git a/qqq-middleware-javalin/src/test/resources/static-site.jar b/qqq-middleware-javalin/src/test/resources/static-site.jar new file mode 100644 index 0000000000000000000000000000000000000000..f5e203e4f9f831591b5b63cd7af1eb4552e8f677 GIT binary patch literal 621 zcmWIWW@h1HVBlb2xH#c#6ay0AWME~0p0C?y-!rFuymj?1@_OrPojY@WbCAIm;|EWR^t^m^Jbf>guG$i0V06v+ zf^pD=0(~!^Gn}783(lQCt9$me&dMuKo<4fY1hkZ$!=cRQoior1M<50{0^;67XzmpS z$`+R-mSiUD7H5{E>ZTRt=jvu97U?70eHcxP3N9^a`T4q;c_7_-86~+n#i!4QpEwij z5%R%HC+G>pQCo|*#|i-T^MM`3$i&5fI~ajVkN`ZSAc4xrB*K6QR%E||f)xR@flRno zq(BbvMp%w5Ob{k)05Wle5W*3lFhcelD2x!`F_4MX2(%Cj@MdKL>0$vwAw~uUS0H9! F00191kAVOH literal 0 HcmV?d00001 From d13fc4a8638ffe5e226ae285fbc13240af605ead Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 15 Jun 2025 10:35:18 -0500 Subject: [PATCH 3/4] Removed - Merged back into overall unit tests --- ...ApplicationJavalinServerClasspathTest.java | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java deleted file mode 100644 index f75b99d1..00000000 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerClasspathTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * QQQ - Low-code Application Framework for Engineers. - * Copyright (C) 2021-2024. Kingsrook, LLC - * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States - * contact@kingsrook.com - * https://github.com/Kingsrook/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package com.kingsrook.qqq.middleware.javalin; - - -import com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - - -/******************************************************************************* - ** Unit test for QApplicationJavalinServer with static files served from classpath - *******************************************************************************/ -class QApplicationJavalinServerClasspathTest -{ - private static final int PORT = 6265; - private QApplicationJavalinServer javalinServer; - - - - /******************************************************************************* - ** - *******************************************************************************/ - @Test - void testStaticRouterFilesFromClasspath() throws Exception - { - System.setProperty("qqq.javalin.enableStaticFilesFromJar", "true"); - - javalinServer = new QApplicationJavalinServer(new QApplicationJavalinServerTest.TestApplication()) - .withServeFrontendMaterialDashboard(false) - .withPort(PORT) - .withAdditionalRouteProvider(new SimpleFileSystemDirectoryRouter("/statically-served-from-jar", "static-site-from-jar/")); - - javalinServer.start(); - - Unirest.config().setDefaultResponseEncoding("UTF-8"); - HttpResponse response = Unirest.get("http://localhost:" + PORT + "/statically-served-from-jar/foo-in-jar.html").asString(); - assertEquals("Foo in a Jar!\n", response.getBody()); - } -} \ No newline at end of file From f6859d040f36a3ec915f22c86cc26473026725d7 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 15 Jun 2025 10:36:11 -0500 Subject: [PATCH 4/4] Refactored to use the constructor instead of the class/static method to load properties - makes unit test runtime cleaning --- .../SimpleFileSystemDirectoryRouter.java | 30 +++++++++---------- .../QApplicationJavalinServerTest.java | 24 +++++++++++++++ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java index 89e424e2..e30c94a9 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/routeproviders/SimpleFileSystemDirectoryRouter.java @@ -51,8 +51,21 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt private static final QLogger LOG = QLogger.getLogger(SimpleFileSystemDirectoryRouter.class); public static boolean loadStaticFilesFromJar = false; - static + + private final String hostedPath; + private final String fileSystemPath; + private QCodeReference routeAuthenticator; + private QInstance qInstance; + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public SimpleFileSystemDirectoryRouter(String hostedPath, String fileSystemPath) { + this.hostedPath = hostedPath; + this.fileSystemPath = fileSystemPath; + /////////////////////////////////////////////////////////////////////////////////////////////////////// // read the property to see if we should load static files from the jar file or from the file system // // Javan only supports loading via one method per path, so its a choice of one or the other... // @@ -72,21 +85,6 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt } } - private final String hostedPath; - private final String fileSystemPath; - private QCodeReference routeAuthenticator; - private QInstance qInstance; - - /******************************************************************************* - ** Constructor - ** - *******************************************************************************/ - public SimpleFileSystemDirectoryRouter(String hostedPath, String fileSystemPath) - { - this.hostedPath = hostedPath; - this.fileSystemPath = fileSystemPath; - } - /*************************************************************************** diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java index 1d3c8e72..a9524022 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/middleware/javalin/QApplicationJavalinServerTest.java @@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.javalin.TestUtils; +import com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter; import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1; import io.javalin.http.HttpStatus; import kong.unirest.HttpResponse; @@ -70,6 +71,7 @@ class QApplicationJavalinServerTest { javalinServer.stop(); TestApplication.callCount = 0; + System.clearProperty("qqq.javalin.enableStaticFilesFromJar"); } @@ -226,6 +228,28 @@ class QApplicationJavalinServerTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testStaticRouterFilesFromClasspath() throws Exception + { + System.setProperty("qqq.javalin.enableStaticFilesFromJar", "true"); + + javalinServer = new QApplicationJavalinServer(new QApplicationJavalinServerTest.TestApplication()) + .withServeFrontendMaterialDashboard(false) + .withPort(PORT) + .withAdditionalRouteProvider(new SimpleFileSystemDirectoryRouter("/statically-served-from-jar", "static-site-from-jar/")); + + javalinServer.start(); + + Unirest.config().setDefaultResponseEncoding("UTF-8"); + HttpResponse response = Unirest.get("http://localhost:" + PORT + "/statically-served-from-jar/foo-in-jar.html").asString(); + assertEquals("Foo in a Jar!\n", response.getBody()); + } + + + /******************************************************************************* ** *******************************************************************************/