Compare commits

...

18 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
15 changed files with 380 additions and 101 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

@ -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

@ -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,6 +117,12 @@
<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>

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,19 @@ 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; 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;
private final String hostedPath;
private final String fileSystemPath;
private QCodeReference routeAuthenticator;
private QInstance qInstance;
/******************************************************************************* /*******************************************************************************
** Constructor ** Constructor
@ -72,8 +75,7 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
try 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(SimpleFileSystemDirectoryRouter.LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "");
String propertyValue = System.getProperty(propertyName, "");
if(propertyValue.equals("true")) if(propertyValue.equals("true"))
{ {
loadStaticFilesFromJar = true; loadStaticFilesFromJar = true;
@ -81,7 +83,8 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
} }
catch(Exception e) catch(Exception e)
{ {
e.printStackTrace(); loadStaticFilesFromJar = false;
LOG.warn("Exception attempting to read system property, defaulting to false. ", e, logPair("system property", SimpleFileSystemDirectoryRouter.LOAD_STATIC_FILES_FROM_JAR_PROPERTY));
} }
} }
@ -93,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());
} }
@ -117,18 +122,17 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
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));
} }
/// ///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// Handle loading static files from the jar OR the filesystem based on system property // // Handle loading static files from the jar OR the filesystem based on system property //
/// ///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
if(SimpleFileSystemDirectoryRouter.loadStaticFilesFromJar) if(SimpleFileSystemDirectoryRouter.loadStaticFilesFromJar)
{ {
staticFileConfig.directory = fileSystemPath; staticFileConfig.directory = fileSystemPath;
staticFileConfig.hostedPath = hostedPath; staticFileConfig.hostedPath = hostedPath;
staticFileConfig.location = Location.CLASSPATH; staticFileConfig.location = Location.CLASSPATH;
LOG.info("Static File Config : hostedPath [" + hostedPath + "] : directory [" + staticFileConfig.directory + "] : location [CLASSPATH]");
} }
else else
{ {
@ -146,9 +150,9 @@ public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInt
staticFileConfig.directory = resource.getFile(); staticFileConfig.directory = resource.getFile();
staticFileConfig.hostedPath = hostedPath; staticFileConfig.hostedPath = hostedPath;
staticFileConfig.location = Location.EXTERNAL; staticFileConfig.location = Location.EXTERNAL;
LOG.info("Static File Config : hostedPath [" + hostedPath + "] : directory [" + staticFileConfig.directory + "] : location [EXTERNAL]");
} }
LOG.info("Static File Config", logPair("hostedPath", hostedPath), logPair("directory", staticFileConfig.directory), logPair("location", staticFileConfig.location));
} }
@ -200,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);
} }
@ -253,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

@ -37,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;
@ -71,7 +72,7 @@ class QApplicationJavalinServerTest
{ {
javalinServer.stop(); javalinServer.stop();
TestApplication.callCount = 0; TestApplication.callCount = 0;
System.clearProperty("qqq.javalin.enableStaticFilesFromJar"); System.clearProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY);
} }
@ -214,7 +215,7 @@ class QApplicationJavalinServerTest
@Test @Test
void testStaticRouterFilesFromExternal() throws Exception void testStaticRouterFilesFromExternal() throws Exception
{ {
System.setProperty("qqq.javalin.enableStaticFilesFromJar", "false"); System.setProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "false");
javalinServer = new QApplicationJavalinServer(getQqqApplication()) javalinServer = new QApplicationJavalinServer(getQqqApplication())
.withServeFrontendMaterialDashboard(false) .withServeFrontendMaterialDashboard(false)
@ -228,13 +229,93 @@ class QApplicationJavalinServerTest
/*******************************************************************************
**
*******************************************************************************/
@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 @Test
void testStaticRouterFilesFromClasspath() throws Exception void testStaticRouterFilesFromClasspath() throws Exception
{ {
System.setProperty("qqq.javalin.enableStaticFilesFromJar", "true"); System.setProperty(LOAD_STATIC_FILES_FROM_JAR_PROPERTY, "true");
javalinServer = new QApplicationJavalinServer(new QApplicationJavalinServerTest.TestApplication()) javalinServer = new QApplicationJavalinServer(new QApplicationJavalinServerTest.TestApplication())
.withServeFrontendMaterialDashboard(false) .withServeFrontendMaterialDashboard(false)

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>