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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CheckStyle-IDEA" serialisationVersion="2"> <component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>9.0.1</checkstyleVersion> <checkstyleVersion>10.3.4</checkstyleVersion>
<scanScope>JavaOnly</scanScope> <scanScope>JavaOnly</scanScope>
<suppressErrors>true</suppressErrors> <suppressErrors>true</suppressErrors>
<option name="thirdPartyClasspath" /> <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> </modules>
<properties> <properties>
<revision>0.25.0</revision> <revision>0.26.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <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.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.function.Function; import java.util.function.Function;
@ -47,7 +48,7 @@ public class TimeZonePossibleValueSourceMetaDataProvider
*******************************************************************************/ *******************************************************************************/
public QPossibleValueSource produce() 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) 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() QPossibleValueSource possibleValueSource = new QPossibleValueSource()
.withName("timeZones") .withName("timeZones")
@ -72,6 +83,11 @@ public class TimeZonePossibleValueSourceMetaDataProvider
} }
} }
if(comparator != null)
{
enumValues.sort(comparator);
}
possibleValueSource.withEnumValues(enumValues); possibleValueSource.withEnumValues(enumValues);
return (possibleValueSource); return (possibleValueSource);
} }

View File

@ -594,6 +594,7 @@ public abstract class QRecordEntity
{ {
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME"); Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
String tableNameValue = (String) tableNameField.get(null); String tableNameValue = (String) tableNameField.get(null);
return (tableNameValue); return (tableNameValue);
} }
catch(Exception e) 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. --> <!-- When updating to javalin 6.3.0, we received classNotFound errors - which this fixed. -->
<kotlin.version>1.9.10</kotlin.version> <kotlin.version>1.9.10</kotlin.version>
<javalin.version>6.3.0</javalin.version> <javalin.version>6.6.0</javalin.version>
</properties> </properties>
<dependencies> <dependencies>
@ -117,11 +117,29 @@
<artifactId>assertj-core</artifactId> <artifactId>assertj-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </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> </dependencies>
<build> <build>
<sourceDirectory>src/main/java</sourceDirectory> <sourceDirectory>src/main/java</sourceDirectory>
<plugins> <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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>

View File

@ -26,6 +26,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; 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.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication; import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
@ -74,9 +75,11 @@ public class QApplicationJavalinServer
private Integer port = 8000; private Integer port = 8000;
private boolean serveFrontendMaterialDashboard = true; 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 boolean serveLegacyUnversionedMiddlewareAPI = true;
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1()); private boolean serveApplicationApi = true;
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null; 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 Consumer<Javalin> javalinConfigurationCustomizer = null;
private QJavalinMetaData javalinMetaData = null; private QJavalinMetaData javalinMetaData = null;
@ -132,20 +135,24 @@ public class QApplicationJavalinServer
{ {
if(resource != null) if(resource != null)
{ {
config.staticFiles.add("/material-dashboard-overlay"); config.staticFiles.add(staticFileConfig ->
{
staticFileConfig.hostedPath = this.frontendMaterialDashboardHostedPath;
staticFileConfig.directory = "/material-dashboard-overlay";
});
} }
} }
//////////////////////////////////////////////////////////////////////////////////// config.staticFiles.add(staticFileConfig ->
// tell javalin where to find material-dashboard static web assets // {
// in this case, this path is coming from the qqq-frontend-material-dashboard jar // staticFileConfig.hostedPath = this.frontendMaterialDashboardHostedPath;
//////////////////////////////////////////////////////////////////////////////////// staticFileConfig.directory = "/material-dashboard";
config.staticFiles.add("/material-dashboard"); });
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// set the index page for the SPA from 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 // // versioned qqq middleware routes //
///////////////////////////////////// /////////////////////////////////////
@ -183,7 +204,7 @@ public class QApplicationJavalinServer
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// additional route providers (e.g., application-apis, other middlewares) // // additional route providers (e.g., application-apis, other middlewares) //
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders)) for(QJavalinRouteProvider routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{ {
routeProvider.setQInstance(qInstance); routeProvider.setQInstance(qInstance);
@ -201,7 +222,7 @@ public class QApplicationJavalinServer
// also pass the javalin service into any additionalRouteProviders, // // also pass the javalin service into any additionalRouteProviders, //
// in case they need additional setup, e.g., before/after handlers. // // 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); 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); 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 ** Getter for serveLegacyUnversionedMiddlewareAPI
*******************************************************************************/ *******************************************************************************/
@ -535,7 +575,7 @@ public class QApplicationJavalinServer
/******************************************************************************* /*******************************************************************************
** Getter for additionalRouteProviders ** Getter for additionalRouteProviders
*******************************************************************************/ *******************************************************************************/
public List<QJavalinRouteProviderInterface> getAdditionalRouteProviders() public List<QJavalinRouteProvider> getAdditionalRouteProviders()
{ {
return (this.additionalRouteProviders); return (this.additionalRouteProviders);
} }
@ -545,7 +585,7 @@ public class QApplicationJavalinServer
/******************************************************************************* /*******************************************************************************
** Setter for additionalRouteProviders ** Setter for additionalRouteProviders
*******************************************************************************/ *******************************************************************************/
public void setAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders) public void setAdditionalRouteProviders(List<QJavalinRouteProvider> additionalRouteProviders)
{ {
this.additionalRouteProviders = additionalRouteProviders; this.additionalRouteProviders = additionalRouteProviders;
} }
@ -555,7 +595,7 @@ public class QApplicationJavalinServer
/******************************************************************************* /*******************************************************************************
** Fluent setter for additionalRouteProviders ** Fluent setter for additionalRouteProviders
*******************************************************************************/ *******************************************************************************/
public QApplicationJavalinServer withAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders) public QApplicationJavalinServer withAdditionalRouteProviders(List<QJavalinRouteProvider> additionalRouteProviders)
{ {
this.additionalRouteProviders = additionalRouteProviders; this.additionalRouteProviders = additionalRouteProviders;
return (this); return (this);
@ -566,7 +606,7 @@ public class QApplicationJavalinServer
/******************************************************************************* /*******************************************************************************
** Fluent setter to add a single additionalRouteProvider ** Fluent setter to add a single additionalRouteProvider
*******************************************************************************/ *******************************************************************************/
public QApplicationJavalinServer withAdditionalRouteProvider(QJavalinRouteProviderInterface additionalRouteProvider) public QApplicationJavalinServer withAdditionalRouteProvider(QJavalinRouteProvider additionalRouteProvider)
{ {
if(this.additionalRouteProviders == null) if(this.additionalRouteProviders == null)
{ {
@ -690,4 +730,57 @@ public class QApplicationJavalinServer
return (this); 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 ** Interface for classes that can provide a list of endpoints to a javalin
** server. ** server.
*******************************************************************************/ *******************************************************************************/
public interface QJavalinRouteProviderInterface public abstract class QJavalinRouteProvider
{ {
/*************************************************************************** /***************************************************************************
** For initial setup when server boots, set the qInstance - but also, ** For initial setup when server boots, set the qInstance - but also,
** e.g., for development, to do a hot-swap. ** 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 // // no endpoints at default //
@ -58,7 +59,7 @@ public interface QJavalinRouteProviderInterface
** accept the javalinConfig object, to perform whatever setup you need, ** accept the javalinConfig object, to perform whatever setup you need,
** such as setting up routes. ** such as setting up routes.
***************************************************************************/ ***************************************************************************/
default void acceptJavalinConfig(JavalinConfig config) public void acceptJavalinConfig(JavalinConfig config)
{ {
///////////////////// /////////////////////
// noop at default // // noop at default //
@ -70,11 +71,10 @@ public interface QJavalinRouteProviderInterface
** accept the Javalin service object, to perform whatever setup you need, ** accept the Javalin service object, to perform whatever setup you need,
** such as setting up before/after handlers. ** such as setting up before/after handlers.
***************************************************************************/ ***************************************************************************/
default void acceptJavalinService(Javalin service) public void acceptJavalinService(Javalin service)
{ {
///////////////////// /////////////////////
// noop at default // // noop at default //
///////////////////// /////////////////////
} }
} }

View File

@ -32,13 +32,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
*******************************************************************************/ *******************************************************************************/
public class JavalinRouteProviderMetaData implements QMetaDataObject public class JavalinRouteProviderMetaData implements QMetaDataObject
{ {
private String hostedPath; private String hostedPath;
private String spaRootPath;
private String fileSystemPath; private String spaRootFile;
private String processName; private String fileSystemPath;
private String processName;
private List<String> methods; private List<String> methods;
private QCodeReference routeAuthenticator; private QCodeReference routeAuthenticator;
@ -206,4 +205,70 @@ public class JavalinRouteProviderMetaData implements QMetaDataObject
return (this); 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.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import com.kingsrook.qqq.backend.javalin.QJavalinUtils; 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.metadata.JavalinRouteProviderMetaData;
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface; import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
import io.javalin.apibuilder.ApiBuilder; 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); 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.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; 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.metadata.JavalinRouteProviderMetaData;
import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface; import com.kingsrook.qqq.middleware.javalin.routeproviders.authentication.RouteAuthenticatorInterface;
import io.javalin.Javalin; 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 ** javalin route provider that hosts a path in the http server via a path on
** the file system ** 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 static final QLogger LOG = QLogger.getLogger(SimpleFileSystemDirectoryRouter.class);
public static boolean loadStaticFilesFromJar = false;
private final String hostedPath; private final String fileSystemPath;
private final String fileSystemPath; private final String hostedPath;
private QCodeReference routeAuthenticator;
private QCodeReference routeAuthenticator; private QInstance qInstance;
private String spaRootPath;
private QInstance qInstance; private String spaRootFile;
@ -67,6 +68,24 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
{ {
this.hostedPath = hostedPath; this.hostedPath = hostedPath;
this.fileSystemPath = fileSystemPath; 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) public SimpleFileSystemDirectoryRouter(JavalinRouteProviderMetaData routeProvider)
{ {
this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath()); this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath());
setSpaRootPath(routeProvider.getSpaRootPath());
setSpaRootFile(routeProvider.getSpaRootFile());
setRouteAuthenticator(routeProvider.getRouteAuthenticator()); setRouteAuthenticator(routeProvider.getRouteAuthenticator());
} }
@ -98,25 +119,40 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
***************************************************************************/ ***************************************************************************/
private void handleJavalinStaticFileConfig(StaticFileConfig staticFileConfig) 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("/")) 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; // Handle loading static files from the jar OR the filesystem based on system property //
staticFileConfig.location = Location.EXTERNAL; /////////////////////////////////////////////////////////////////////////////////////////
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 @Override
public void acceptJavalinConfig(JavalinConfig config) public void acceptJavalinConfig(JavalinConfig config)
{ {
if(this.getSpaRootPath() != null && !this.getSpaRootPath().isEmpty())
{
config.spaRoot.addFile(this.spaRootPath, this.spaRootFile);
}
config.staticFiles.add(this::handleJavalinStaticFileConfig); config.staticFiles.add(this::handleJavalinStaticFileConfig);
} }
@ -221,4 +261,74 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
return (this); 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.instances.AbstractQQQApplication;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.javalin.TestUtils; 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 com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
import io.javalin.http.HttpStatus; import io.javalin.http.HttpStatus;
import kong.unirest.HttpResponse; import kong.unirest.HttpResponse;
@ -36,6 +37,7 @@ import kong.unirest.Unirest;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; 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.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; 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(); javalinServer.stop();
TestApplication.callCount = 0; 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> <dependency>
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-material-dashboard</artifactId> <artifactId>qqq-frontend-material-dashboard</artifactId>
<version>0.24.0</version> <version>0.26.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>