Compare commits

...

27 Commits

Author SHA1 Message Date
9c60c483aa Checkpoint Commit
- Working Unified (qqq+kof22website) project setup and configuration working with IntelliJ 2025
2025-07-17 18:50:49 -05:00
753f8bb276 Reverted typo 2025-07-08 16:53:56 -05:00
a131bc782e Checkpoint Commit - Working larger test data and frontend polishing 2025-07-02 09:46:39 -05:00
6262974a4c Added full support for defining SPAs within the metadata for a route provider (both spa hosted path, and physical file path) 2025-06-24 16:59:35 -05:00
fe0c9f4b9c Test commit - sync between machines 2025-06-22 08:11:08 -05:00
b93f262622 Initial refactoring to abstract baseclass to allow for additional functionality across all routers 2025-06-17 15:16:57 -05:00
a6047dcc18 Initial refactoring to abstract baseclass to allow for additional functionality across all routers 2025-06-17 15:16:50 -05:00
fecfb5c19a Updated test files for positive and negative tests cases 2025-06-17 15:15:41 -05:00
fca857cf98 Updated test files for positive and negative tests cases 2025-06-17 15:15:14 -05:00
e558450f6b Updated checkstyle base version 2025-06-17 15:14:40 -05:00
d3ce24d00e Ignore local IntelliJ config files 2025-06-17 14:54:50 -05:00
7575a57ae9 Updated Property Var name per review 2025-06-17 13:05:14 -05:00
54f40fbc83 Added exception to warning message per review 2025-06-17 13:02:28 -05:00
caa6723cd2 Updated comments on Getters and Settings to see the flutter method for details. 2025-06-17 13:00:34 -05:00
b21ea60c80 Updated static var references (to class from instance) 2025-06-17 09:45:25 -05:00
ea15640db1 Cleaned up logging and converted to LogPairs per DK feedback 2025-06-17 09:42:12 -05:00
9cb401a20e Added support (and tests) for overriding the default hosted path for the material-frontend-ui 2025-06-17 09:41:45 -05:00
010b64a0d3 Added for valid local tests of loading the front-end UI from different hosted paths 2025-06-17 09:41:07 -05:00
46bca6efb9 Merge pull request #184 from Kingsrook/183-javalin-server-fails-to-start-when-using-static-files-in-a-production-jar
Fixed loading static files from FS or Jars
2025-06-15 11:04:52 -05:00
f6859d040f Refactored to use the constructor instead of the class/static method to load properties - makes unit test runtime cleaning 2025-06-15 10:36:11 -05:00
d13fc4a863 Removed - Merged back into overall unit tests 2025-06-15 10:35:18 -05:00
eab87b9d80 Added missing jar for unit test 2025-06-15 10:01:11 -05:00
707400a8b2 Added support for loading static files from the filesystem as as from jars (based on a system property) 2025-06-14 16:07:51 -05:00
ffca465f04 Add option to specify Comparator, for custom sorting of options [skip ci] 2025-06-05 10:59:48 -05:00
dfb584b367 Updating to 0.26.0 2025-05-19 15:20:47 -05:00
504c53b108 Merge tag 'version-0.25.0' into dev
Tag release
2025-05-19 15:20:43 -05:00
3395ee2146 Update for next development version 2025-05-19 15:05:04 -05:00
19 changed files with 502 additions and 119 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>9.0.1</checkstyleVersion>
<checkstyleVersion>10.3.4</checkstyleVersion>
<scanScope>JavaOnly</scanScope>
<suppressErrors>true</suppressErrors>
<option name="thirdPartyClasspath" />

29
.idea/compiler.xml generated
View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="qqq-backend-module-rdbms" />
<module name="qqq-middleware-picocli" />
<module name="qqq-backend-module-filesystem" />
<module name="qqq-backend-core" />
<module name="qqq-middleware-javalin" />
<module name="qqq-sample-project" />
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="qqq-backend-core" options="-Xlint:unchecked" />
<module name="qqq-backend-module-filesystem" options="-Xlint:unchecked" />
<module name="qqq-backend-module-rdbms" options="-Xlint:unchecked" />
<module name="qqq-middleware-javalin" options="-Xlint:unchecked" />
<module name="qqq-middleware-picocli" options="-Xlint:unchecked" />
<module name="qqq-parent-project" options="" />
<module name="qqq-sample-project" options="-Xlint:unchecked" />
</option>
</component>
</project>

19
.idea/encodings.xml generated
View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/qqq-backend-core/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-backend-core/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-backend-module-filesystem/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-backend-module-filesystem/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-backend-module-rdbms/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-backend-module-rdbms/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-middleware-javalin/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-middleware-javalin/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-middleware-picocli/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-middleware-picocli/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-sample-project/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/qqq-sample-project/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

View File

@ -48,7 +48,7 @@
</modules>
<properties>
<revision>0.25.0</revision>
<revision>0.26.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.common;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TimeZone;
import java.util.function.Function;
@ -47,7 +48,7 @@ public class TimeZonePossibleValueSourceMetaDataProvider
*******************************************************************************/
public QPossibleValueSource produce()
{
return (produce(null, null));
return (produce(null, null, null));
}
@ -56,6 +57,16 @@ public class TimeZonePossibleValueSourceMetaDataProvider
**
*******************************************************************************/
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper)
{
return (produce(filter, labelMapper, null));
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper, Comparator<QPossibleValue<?>> comparator)
{
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
.withName("timeZones")
@ -72,6 +83,11 @@ public class TimeZonePossibleValueSourceMetaDataProvider
}
}
if(comparator != null)
{
enumValues.sort(comparator);
}
possibleValueSource.withEnumValues(enumValues);
return (possibleValueSource);
}

View File

@ -594,6 +594,7 @@ public abstract class QRecordEntity
{
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
String tableNameValue = (String) tableNameField.get(null);
return (tableNameValue);
}
catch(Exception e)

View File

@ -1 +1 @@
0.25.0
0.26.0

View File

@ -36,7 +36,7 @@
<!-- When updating to javalin 6.3.0, we received classNotFound errors - which this fixed. -->
<kotlin.version>1.9.10</kotlin.version>
<javalin.version>6.3.0</javalin.version>
<javalin.version>6.6.0</javalin.version>
</properties>
<dependencies>
@ -117,11 +117,29 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-api</artifactId>
<version>0.26.0-integration-20250615-161253</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>
${project.basedir}/src/test/resources/static-site.jar
</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

View File

@ -26,6 +26,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.kingsrook.qqq.api.javalin.QJavalinApiHandler;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
@ -74,9 +75,11 @@ public class QApplicationJavalinServer
private Integer port = 8000;
private boolean serveFrontendMaterialDashboard = true;
private String frontendMaterialDashboardHostedPath = "/"; // TODO - Things like this should be moved into a central configuration file system, so that it can be changed in userspace without code changes.
private boolean serveLegacyUnversionedMiddlewareAPI = true;
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1());
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null;
private boolean serveApplicationApi = true;
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1()); // TODO - Seems like this should be null by default, and only set if the application developer wants to serve versioned middleware APIs. @DK
private List<QJavalinRouteProvider> additionalRouteProviders = null;
private Consumer<Javalin> javalinConfigurationCustomizer = null;
private QJavalinMetaData javalinMetaData = null;
@ -132,20 +135,24 @@ public class QApplicationJavalinServer
{
if(resource != null)
{
config.staticFiles.add("/material-dashboard-overlay");
config.staticFiles.add(staticFileConfig ->
{
staticFileConfig.hostedPath = this.frontendMaterialDashboardHostedPath;
staticFileConfig.directory = "/material-dashboard-overlay";
});
}
}
////////////////////////////////////////////////////////////////////////////////////
// tell javalin where to find material-dashboard static web assets //
// in this case, this path is coming from the qqq-frontend-material-dashboard jar //
////////////////////////////////////////////////////////////////////////////////////
config.staticFiles.add("/material-dashboard");
config.staticFiles.add(staticFileConfig ->
{
staticFileConfig.hostedPath = this.frontendMaterialDashboardHostedPath;
staticFileConfig.directory = "/material-dashboard";
});
////////////////////////////////////////////////////////////
// set the index page for the SPA from material dashboard //
////////////////////////////////////////////////////////////
config.spaRoot.addFile("/", "material-dashboard/index.html");
config.spaRoot.addFile(this.frontendMaterialDashboardHostedPath, "material-dashboard/index.html");
}
///////////////////////////////////////////
@ -167,6 +174,20 @@ public class QApplicationJavalinServer
}
}
if(serveApplicationApi)
{
try
{
QJavalinApiHandler qJavalinApiHandler = new QJavalinApiHandler(qInstance);
config.router.apiBuilder(qJavalinApiHandler.getRoutes());
}
catch(Exception e)
{
LOG.error("Unable to add application API routes to Javalin service.", e);
throw new RuntimeException(e);
}
}
/////////////////////////////////////
// versioned qqq middleware routes //
/////////////////////////////////////
@ -183,7 +204,7 @@ public class QApplicationJavalinServer
////////////////////////////////////////////////////////////////////////////
// additional route providers (e.g., application-apis, other middlewares) //
////////////////////////////////////////////////////////////////////////////
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
for(QJavalinRouteProvider routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{
routeProvider.setQInstance(qInstance);
@ -201,7 +222,7 @@ public class QApplicationJavalinServer
// also pass the javalin service into any additionalRouteProviders, //
// in case they need additional setup, e.g., before/after handlers. //
//////////////////////////////////////////////////////////////////////
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
for(QJavalinRouteProvider routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{
routeProvider.acceptJavalinService(service);
}
@ -379,7 +400,7 @@ public class QApplicationJavalinServer
}
}
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
for(QJavalinRouteProvider routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{
routeProvider.setQInstance(newQInstance);
}
@ -470,6 +491,25 @@ public class QApplicationJavalinServer
/*******************************************************************************
* Sets the hosted path for the frontend Material Dashboard UI.
*
* This value determines the base URL path under which the static frontend
* dashboard assets are served. It should match the path configured in your
* frontend build or static asset router.
*
* @param frontendMaterialDashboardHostedPath the hosted path (e.g., "/admin" or "/dashboard"). Default is "/"
*
* @see #withServeFrontendMaterialDashboard(boolean)
*******************************************************************************/
public QApplicationJavalinServer withFrontendMaterialDashboardHostedPath(String frontendMaterialDashboardHostedPath)
{
this.frontendMaterialDashboardHostedPath = frontendMaterialDashboardHostedPath;
return (this);
}
/*******************************************************************************
** Getter for serveLegacyUnversionedMiddlewareAPI
*******************************************************************************/
@ -535,7 +575,7 @@ public class QApplicationJavalinServer
/*******************************************************************************
** Getter for additionalRouteProviders
*******************************************************************************/
public List<QJavalinRouteProviderInterface> getAdditionalRouteProviders()
public List<QJavalinRouteProvider> getAdditionalRouteProviders()
{
return (this.additionalRouteProviders);
}
@ -545,7 +585,7 @@ public class QApplicationJavalinServer
/*******************************************************************************
** Setter for additionalRouteProviders
*******************************************************************************/
public void setAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders)
public void setAdditionalRouteProviders(List<QJavalinRouteProvider> additionalRouteProviders)
{
this.additionalRouteProviders = additionalRouteProviders;
}
@ -555,7 +595,7 @@ public class QApplicationJavalinServer
/*******************************************************************************
** Fluent setter for additionalRouteProviders
*******************************************************************************/
public QApplicationJavalinServer withAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders)
public QApplicationJavalinServer withAdditionalRouteProviders(List<QJavalinRouteProvider> additionalRouteProviders)
{
this.additionalRouteProviders = additionalRouteProviders;
return (this);
@ -566,7 +606,7 @@ public class QApplicationJavalinServer
/*******************************************************************************
** Fluent setter to add a single additionalRouteProvider
*******************************************************************************/
public QApplicationJavalinServer withAdditionalRouteProvider(QJavalinRouteProviderInterface additionalRouteProvider)
public QApplicationJavalinServer withAdditionalRouteProvider(QJavalinRouteProvider additionalRouteProvider)
{
if(this.additionalRouteProviders == null)
{
@ -690,4 +730,57 @@ public class QApplicationJavalinServer
return (this);
}
/*******************************************************************************
** Getter for frontendMaterialDashboardHostedPath
*
* @see #withFrontendMaterialDashboardHostedPath(String)
*******************************************************************************/
public String getFrontendMaterialDashboardHostedPath()
{
return (this.frontendMaterialDashboardHostedPath);
}
/*******************************************************************************
** Setter for frontendMaterialDashboardHostedPath
*
* @see #withFrontendMaterialDashboardHostedPath(String)
*******************************************************************************/
public void setFrontendMaterialDashboardHostedPath(String frontendMaterialDashboardHostedPath)
{
this.frontendMaterialDashboardHostedPath = frontendMaterialDashboardHostedPath;
}
/*******************************************************************************
** Getter for serveApplicationApi
*******************************************************************************/
public boolean getServeApplicationApi()
{
return (this.serveApplicationApi);
}
/*******************************************************************************
** Setter for serveApplicationApi
*******************************************************************************/
public void setServeApplicationApi(boolean serveApplicationApi)
{
this.serveApplicationApi = serveApplicationApi;
}
/*******************************************************************************
** Fluent setter for serveApplicationApi
*******************************************************************************/
public QApplicationJavalinServer withServeApplicationApi(boolean serveApplicationApi)
{
this.serveApplicationApi = serveApplicationApi;
return (this);
}
}

View File

@ -32,19 +32,20 @@ import io.javalin.config.JavalinConfig;
** Interface for classes that can provide a list of endpoints to a javalin
** server.
*******************************************************************************/
public interface QJavalinRouteProviderInterface
public abstract class QJavalinRouteProvider
{
/***************************************************************************
** For initial setup when server boots, set the qInstance - but also,
** e.g., for development, to do a hot-swap.
***************************************************************************/
void setQInstance(QInstance qInstance);
public abstract void setQInstance(QInstance qInstance);
/***************************************************************************
**
***************************************************************************/
default EndpointGroup getJavalinEndpointGroup()
public EndpointGroup getJavalinEndpointGroup()
{
/////////////////////////////
// no endpoints at default //
@ -58,7 +59,7 @@ public interface QJavalinRouteProviderInterface
** accept the javalinConfig object, to perform whatever setup you need,
** such as setting up routes.
***************************************************************************/
default void acceptJavalinConfig(JavalinConfig config)
public void acceptJavalinConfig(JavalinConfig config)
{
/////////////////////
// noop at default //
@ -70,11 +71,10 @@ public interface QJavalinRouteProviderInterface
** accept the Javalin service object, to perform whatever setup you need,
** such as setting up before/after handlers.
***************************************************************************/
default void acceptJavalinService(Javalin service)
public void acceptJavalinService(Javalin service)
{
/////////////////////
// noop at default //
/////////////////////
}
}

View File

@ -32,13 +32,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
*******************************************************************************/
public class JavalinRouteProviderMetaData implements QMetaDataObject
{
private String hostedPath;
private String fileSystemPath;
private String processName;
private List<String> methods;
private String hostedPath;
private String spaRootPath;
private String spaRootFile;
private String fileSystemPath;
private String processName;
private List<String> methods;
private QCodeReference routeAuthenticator;
@ -206,4 +205,70 @@ public class JavalinRouteProviderMetaData implements QMetaDataObject
return (this);
}
/*******************************************************************************
** Getter for spaRootPath
*******************************************************************************/
public String getSpaRootPath()
{
return (this.spaRootPath);
}
/*******************************************************************************
** Setter for spaRootPath
*******************************************************************************/
public void setSpaRootPath(String spaRootPath)
{
this.spaRootPath = spaRootPath;
}
/*******************************************************************************
** Fluent setter for spaRootPath
*******************************************************************************/
public JavalinRouteProviderMetaData withSpaRootPath(String spaRootPath)
{
this.spaRootPath = spaRootPath;
return (this);
}
/*******************************************************************************
* Getter for spaRootFile
* @see #withSpaRootFile(String)
*******************************************************************************/
public String getSpaRootFile()
{
return (this.spaRootFile);
}
/*******************************************************************************
* Setter for spaRootFile
* @see #withSpaRootFile(String)
*******************************************************************************/
public void setSpaRootFile(String spaRootFile)
{
this.spaRootFile = spaRootFile;
}
/*******************************************************************************
* Fluent setter for spaRootFile
* @param spaRootFile TODO document this property
* @return this
*******************************************************************************/
public JavalinRouteProviderMetaData withSpaRootFile(String spaRootFile)
{
this.spaRootFile = spaRootFile;
return (this);
}
}

View File

@ -43,7 +43,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProvider;
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
import io.javalin.apibuilder.ApiBuilder;
@ -56,7 +56,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
**
*******************************************************************************/
public class ProcessBasedRouter implements QJavalinRouteProviderInterface
public class ProcessBasedRouter extends QJavalinRouteProvider
{
private static final QLogger LOG = QLogger.getLogger(ProcessBasedRouter.class);

View File

@ -31,7 +31,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProvider;
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
import io.javalin.Javalin;
@ -46,16 +46,17 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
** javalin route provider that hosts a path in the http server via a path on
** the file system
*******************************************************************************/
public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInterface
public class SimpleFileSystemDirectoryRouter extends QJavalinRouteProvider
{
public static final String LOAD_STATIC_FILES_FROM_JAR_PROPERTY = "qqq.javalin.enableStaticFilesFromJar";
private static final QLogger LOG = QLogger.getLogger(SimpleFileSystemDirectoryRouter.class);
private final String hostedPath;
private final String fileSystemPath;
private QCodeReference routeAuthenticator;
private QInstance qInstance;
public static boolean loadStaticFilesFromJar = false;
private final String fileSystemPath;
private final String hostedPath;
private QCodeReference routeAuthenticator;
private QInstance qInstance;
private String spaRootPath;
private String spaRootFile;
@ -67,6 +68,24 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
{
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... //
///////////////////////////////////////////////////////////////////////////////////////////////////////
try
{
String propertyValue = System.getProperty(SimpleFileSystemDirectoryRouter.LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "");
if(propertyValue.equals("true"))
{
loadStaticFilesFromJar = true;
}
}
catch(Exception e)
{
loadStaticFilesFromJar = false;
LOG.warn("Exception attempting to read system property, defaulting to false. ", e, logPair("system property", SimpleFileSystemDirectoryRouter.LOAD_STATIC_FILES_FROM_JAR_PROPERTY));
}
}
@ -77,6 +96,8 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
public SimpleFileSystemDirectoryRouter(JavalinRouteProviderMetaData routeProvider)
{
this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath());
setSpaRootPath(routeProvider.getSpaRootPath());
setSpaRootFile(routeProvider.getSpaRootFile());
setRouteAuthenticator(routeProvider.getRouteAuthenticator());
}
@ -98,25 +119,40 @@ 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...");
LOG.warn("hostedPath should probably start with a leading slash...", logPair("hostedPath", hostedPath));
}
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;
}
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", logPair("hostedPath", hostedPath), logPair("directory", staticFileConfig.directory), logPair("location", staticFileConfig.location));
}
@ -168,6 +204,10 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
@Override
public void acceptJavalinConfig(JavalinConfig config)
{
if(this.getSpaRootPath() != null && !this.getSpaRootPath().isEmpty())
{
config.spaRoot.addFile(this.spaRootPath, this.spaRootFile);
}
config.staticFiles.add(this::handleJavalinStaticFileConfig);
}
@ -221,4 +261,74 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
return (this);
}
/*******************************************************************************
* Getter for spaRootPath
* @see #withSpaRootPath(String)
*******************************************************************************/
public String getSpaRootPath()
{
return (this.spaRootPath);
}
/*******************************************************************************
* Setter for spaRootPath
* @see #withSpaRootPath(String)
*******************************************************************************/
public void setSpaRootPath(String spaRootPath)
{
this.spaRootPath = spaRootPath;
}
/*******************************************************************************
* Fluent setter for spaRootPath
* @param spaRootPath TODO document this property
* @return this
*******************************************************************************/
public SimpleFileSystemDirectoryRouter withSpaRootPath(String spaRootPath)
{
this.spaRootPath = spaRootPath;
return (this);
}
/*******************************************************************************
* Getter for spaRootFile
* @see #withSpaRootFile(String)
*******************************************************************************/
public String getSpaRootFile()
{
return (this.spaRootFile);
}
/*******************************************************************************
* Setter for spaRootFile
* @see #withSpaRootFile(String)
*******************************************************************************/
public void setSpaRootFile(String spaRootFile)
{
this.spaRootFile = spaRootFile;
}
/*******************************************************************************
* Fluent setter for spaRootFile
* @param spaRootFile TODO document this property
* @return this
*******************************************************************************/
public SimpleFileSystemDirectoryRouter withSpaRootFile(String spaRootFile)
{
this.spaRootFile = spaRootFile;
return (this);
}
}

View File

@ -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;
@ -36,6 +37,7 @@ import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter.LOAD_STATIC_FILES_FROM_JAR_PROPERTY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -52,6 +54,16 @@ class QApplicationJavalinServerTest
/***************************************************************************
**
***************************************************************************/
private static AbstractQQQApplication getQqqApplication()
{
return new TestApplication();
}
/*******************************************************************************
**
*******************************************************************************/
@ -60,6 +72,7 @@ class QApplicationJavalinServerTest
{
javalinServer.stop();
TestApplication.callCount = 0;
System.clearProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY);
}
@ -196,6 +209,128 @@ class QApplicationJavalinServerTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testStaticRouterFilesFromExternal() throws Exception
{
System.setProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "false");
javalinServer = new QApplicationJavalinServer(getQqqApplication())
.withServeFrontendMaterialDashboard(false)
.withPort(PORT);
javalinServer.start();
Unirest.config().setDefaultResponseEncoding("UTF-8");
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/statically-served/foo.html").asString();
assertEquals("Foo? Bar!", response.getBody());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFrontendMaterialDashboardHostedPathDefault() throws Exception
{
javalinServer = new QApplicationJavalinServer(getQqqApplication())
.withServeFrontendMaterialDashboard(true)
.withPort(PORT)
.withFrontendMaterialDashboardHostedPath("/");
javalinServer.start();
//////////////////////////////////////////////////////
// Verify that we can get access the file correctly //
//////////////////////////////////////////////////////
Unirest.config().setDefaultResponseEncoding("UTF-8");
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/dashboard.html").asString();
assertEquals(200, response.getStatus());
assertEquals("This is a mock of /material-dashboard/dashboard.html for testing purposes.", response.getBody());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFrontendMaterialDashboardHostedPathDefaultInverse() throws Exception
{
javalinServer = new QApplicationJavalinServer(getQqqApplication())
.withServeFrontendMaterialDashboard(true)
.withPort(PORT)
.withFrontendMaterialDashboardHostedPath("/");
javalinServer.start();
////////////////////////////////////////////////////////////
// Verify that the file is not accessible at the app path //
////////////////////////////////////////////////////////////
Unirest.config().setDefaultResponseEncoding("UTF-8");
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/bs-directory/dashboard.html").asString();
/////////////////////////////////////////////////////////////////////////////////
// Note, this will not 404, instead it will return the default index.html file //
/////////////////////////////////////////////////////////////////////////////////
assertEquals("This is a mock of /material-dashboard/index.html for testing purposes.", response.getBody());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFrontendMaterialDashboardHostedPathCustomApp() throws Exception
{
javalinServer = new QApplicationJavalinServer(getQqqApplication())
.withServeFrontendMaterialDashboard(true)
.withPort(PORT)
.withFrontendMaterialDashboardHostedPath("/app");
javalinServer.start();
Unirest.config().setDefaultResponseEncoding("UTF-8");
//////////////////////////////////////////////////////
// verify that we can get access the file correctly //
//////////////////////////////////////////////////////
HttpResponse<String> response1 = Unirest.get("http://localhost:" + PORT + "/app/dashboard.html").asString();
assertEquals(200, response1.getStatus());
assertEquals("This is a mock of /material-dashboard/dashboard.html for testing purposes.", response1.getBody());
/////////////////////////////////////////////////////////////
// Verify that the file is not accessible at the root path //
/////////////////////////////////////////////////////////////
HttpResponse<String> response2 = Unirest.get("http://localhost:" + PORT + "/index.html").asString();
assertEquals(404, response2.getStatus());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testStaticRouterFilesFromClasspath() throws Exception
{
System.setProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "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<String> response = Unirest.get("http://localhost:" + PORT + "/statically-served-from-jar/foo-in-jar.html").asString();
assertEquals("Foo in a Jar!\n", response.getBody());
}
/*******************************************************************************
**
*******************************************************************************/
@ -296,16 +431,6 @@ class QApplicationJavalinServerTest
/***************************************************************************
**
***************************************************************************/
private static AbstractQQQApplication getQqqApplication()
{
return new TestApplication();
}
/***************************************************************************
**
***************************************************************************/

View File

@ -0,0 +1 @@
This is a mock of /material-dashboard-overlay/overlay.html for testing purposes.

View File

@ -0,0 +1 @@
This is a mock of /material-dashboard/dashboard.html for testing purposes.

View File

@ -0,0 +1 @@
This is a mock of /material-dashboard/index.html for testing purposes.

View File

@ -68,7 +68,7 @@
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-material-dashboard</artifactId>
<version>0.24.0</version>
<version>0.26.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>