CE-1887 Initial checkin

This commit is contained in:
2024-10-17 12:28:43 -05:00
parent 872e125810
commit 8500a2559c
3 changed files with 826 additions and 0 deletions

View File

@ -0,0 +1,510 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin;
import java.util.List;
import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
import io.javalin.Javalin;
import io.javalin.http.Context;
import org.apache.commons.lang.BooleanUtils;
/*******************************************************************************
** Second-generation qqq javalin server.
**
** An evolution over the original QJavalinImplementation, which both managed
** the javalin instance itself, but also provided all of the endpoint handlers...
** This class instead just configures & starts the server.
**
** Makes several setters available, to let application-developer choose what
** standard qqq endpoints are served (e.g., frontend-material-dashboard, the
** legacy-unversioned middleware, newer versioned-middleware, and additional qqq
** modules or application-defined services (both provided as instances of
** QJavalinRouteProviderInterface).
**
** System property `qqq.javalin.hotSwapInstance` (defaults to false), causes the
** QInstance to be re-loaded every X millis, to avoid some server restarts while
** doing dev.
*******************************************************************************/
public class QApplicationJavalinServer
{
private static final QLogger LOG = QLogger.getLogger(QApplicationJavalinServer.class);
private final AbstractQQQApplication application;
private Integer port = 8000;
private boolean serveFrontendMaterialDashboard = true;
private boolean serveLegacyUnversionedMiddlewareAPI = true;
private List<AbstractMiddlewareVersion> middlewareVersionList = List.of(new MiddlewareVersionV1());
private List<QJavalinRouteProviderInterface> additionalRouteProviders = null;
private Consumer<Javalin> javalinConfigurationCustomizer = null;
private long lastQInstanceHotSwapMillis;
private long millisBetweenHotSwaps = 2500;
private Consumer<QInstance> hotSwapCustomizer = null;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QApplicationJavalinServer(AbstractQQQApplication application)
{
this.application = application;
}
/***************************************************************************
**
***************************************************************************/
public void start() throws QException
{
QInstance qInstance = application.defineValidatedQInstance();
Javalin service = Javalin.create(config ->
{
if(serveFrontendMaterialDashboard)
{
////////////////////////////////////////////////////////////////////////////////////////
// If you have any assets to add to the web server (e.g., logos, icons) place them at //
// src/main/resources/material-dashboard-overlay (or a directory of your choice //
// under src/main/resources) and use this line of code to tell javalin about it. //
// Make sure to add your app-specific directory to the javalin config before the core //
// material-dashboard directory, so in case the same file exists in both (e.g., //
// favicon.png), the app-specific one will be used. //
////////////////////////////////////////////////////////////////////////////////////////
config.staticFiles.add("/material-dashboard-overlay");
/////////////////////////////////////////////////////////////////////
// tell javalin where to find material-dashboard static web assets //
/////////////////////////////////////////////////////////////////////
config.staticFiles.add("/material-dashboard");
////////////////////////////////////////////////////////////
// set the index page for the SPA from material dashboard //
////////////////////////////////////////////////////////////
config.spaRoot.addFile("/", "material-dashboard/index.html");
}
///////////////////////////////////////////
// add qqq routes to the javalin service //
///////////////////////////////////////////
if(serveLegacyUnversionedMiddlewareAPI)
{
try
{
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(qInstance);
config.router.apiBuilder(qJavalinImplementation.getRoutes());
}
catch(QInstanceValidationException e)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// we should be pretty comfortable that this won't happen, because we've pre-validated the instance above... //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
throw new RuntimeException(e);
}
}
/////////////////////////////////////
// versioned qqq middleware routes //
/////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(middlewareVersionList))
{
config.router.apiBuilder(new QMiddlewareApiSpecHandler(middlewareVersionList).defineJavalinEndpointGroup());
for(AbstractMiddlewareVersion version : middlewareVersionList)
{
version.setQInstance(qInstance);
config.router.apiBuilder(version.getJavalinEndpointGroup(qInstance));
}
}
////////////////////////////////////////////////////////////////////////////
// additional route providers (e.g., application-apis, other middlewares) //
////////////////////////////////////////////////////////////////////////////
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{
routeProvider.setQInstance(qInstance);
config.router.apiBuilder(routeProvider.getJavalinEndpointGroup());
}
});
//////////////////////////////////////////////////////////////////////////////////////
// per system property, set the server to hot-swap the q instance before all routes //
//////////////////////////////////////////////////////////////////////////////////////
String hotSwapPropertyValue = System.getProperty("qqq.javalin.hotSwapInstance", "false");
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(hotSwapPropertyValue)))
{
LOG.info("Server will hotSwap QInstance before requests every [" + millisBetweenHotSwaps + "] millis.");
service.before(context -> hotSwapQInstance());
}
service.before((Context context) -> context.header("Content-Type", "application/json"));
service.after(QJavalinImplementation::clearQContext);
////////////////////////////////////////////////
// allow a configuration-customizer to be run //
////////////////////////////////////////////////
if(javalinConfigurationCustomizer != null)
{
javalinConfigurationCustomizer.accept(service);
}
service.start(port);
}
/*******************************************************************************
** If there's a qInstanceHotSwapSupplier, and its been a little while, replace
** the qInstance with a new one from the supplier. Meant to be used while doing
** development.
*******************************************************************************/
public void hotSwapQInstance()
{
long now = System.currentTimeMillis();
if(now - lastQInstanceHotSwapMillis < millisBetweenHotSwaps)
{
return;
}
lastQInstanceHotSwapMillis = now;
try
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// clear the cache of classes in this class, so that new classes can be found if a meta-data-producer is being used //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ClassPathUtils.clearTopLevelClassCache();
///////////////////////////////////////////////////////////////
// try to get a new, validated instance from the application //
///////////////////////////////////////////////////////////////
QInstance newQInstance = application.defineValidatedQInstance();
if(newQInstance == null)
{
LOG.warn("Got a null qInstance from the application.defineQInstance(). Not hot-swapping.");
return;
}
////////////////////////////////////////
// allow a hot-swap customizer to run //
////////////////////////////////////////
if(hotSwapCustomizer != null)
{
hotSwapCustomizer.accept(newQInstance);
}
///////////////////////////////////////////////////////////////////////
// pass the new qInstance into all of the objects serving qqq routes //
///////////////////////////////////////////////////////////////////////
if(serveLegacyUnversionedMiddlewareAPI)
{
QJavalinImplementation.setQInstance(newQInstance);
}
if(CollectionUtils.nullSafeHasContents(middlewareVersionList))
{
for(AbstractMiddlewareVersion spec : CollectionUtils.nonNullList(middlewareVersionList))
{
spec.setQInstance(newQInstance);
}
}
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
{
routeProvider.setQInstance(newQInstance);
}
LOG.info("Swapped qInstance");
}
catch(QInstanceValidationException e)
{
LOG.error("Validation Error while hot-swapping QInstance", e);
}
catch(Exception e)
{
LOG.error("Error hot-swapping QInstance", e);
}
}
/*******************************************************************************
** Getter for port
*******************************************************************************/
public Integer getPort()
{
return (this.port);
}
/*******************************************************************************
** Setter for port
*******************************************************************************/
public void setPort(Integer port)
{
this.port = port;
}
/*******************************************************************************
** Fluent setter for port
*******************************************************************************/
public QApplicationJavalinServer withPort(Integer port)
{
this.port = port;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void setMillisBetweenHotSwaps(long millisBetweenHotSwaps)
{
this.millisBetweenHotSwaps = millisBetweenHotSwaps;
}
/*******************************************************************************
** Getter for serveFrontendMaterialDashboard
*******************************************************************************/
public boolean getServeFrontendMaterialDashboard()
{
return (this.serveFrontendMaterialDashboard);
}
/*******************************************************************************
** Setter for serveFrontendMaterialDashboard
*******************************************************************************/
public void setServeFrontendMaterialDashboard(boolean serveFrontendMaterialDashboard)
{
this.serveFrontendMaterialDashboard = serveFrontendMaterialDashboard;
}
/*******************************************************************************
** Fluent setter for serveFrontendMaterialDashboard
*******************************************************************************/
public QApplicationJavalinServer withServeFrontendMaterialDashboard(boolean serveFrontendMaterialDashboard)
{
this.serveFrontendMaterialDashboard = serveFrontendMaterialDashboard;
return (this);
}
/*******************************************************************************
** Getter for serveLegacyUnversionedMiddlewareAPI
*******************************************************************************/
public boolean getServeLegacyUnversionedMiddlewareAPI()
{
return (this.serveLegacyUnversionedMiddlewareAPI);
}
/*******************************************************************************
** Setter for serveLegacyUnversionedMiddlewareAPI
*******************************************************************************/
public void setServeLegacyUnversionedMiddlewareAPI(boolean serveLegacyUnversionedMiddlewareAPI)
{
this.serveLegacyUnversionedMiddlewareAPI = serveLegacyUnversionedMiddlewareAPI;
}
/*******************************************************************************
** Fluent setter for serveLegacyUnversionedMiddlewareAPI
*******************************************************************************/
public QApplicationJavalinServer withServeLegacyUnversionedMiddlewareAPI(boolean serveLegacyUnversionedMiddlewareAPI)
{
this.serveLegacyUnversionedMiddlewareAPI = serveLegacyUnversionedMiddlewareAPI;
return (this);
}
/*******************************************************************************
** Getter for middlewareVersionList
*******************************************************************************/
public List<AbstractMiddlewareVersion> getMiddlewareVersionList()
{
return (this.middlewareVersionList);
}
/*******************************************************************************
** Setter for middlewareVersionList
*******************************************************************************/
public void setMiddlewareVersionList(List<AbstractMiddlewareVersion> middlewareVersionList)
{
this.middlewareVersionList = middlewareVersionList;
}
/*******************************************************************************
** Fluent setter for middlewareVersionList
*******************************************************************************/
public QApplicationJavalinServer withMiddlewareVersionList(List<AbstractMiddlewareVersion> middlewareVersionList)
{
this.middlewareVersionList = middlewareVersionList;
return (this);
}
/*******************************************************************************
** Getter for additionalRouteProviders
*******************************************************************************/
public List<QJavalinRouteProviderInterface> getAdditionalRouteProviders()
{
return (this.additionalRouteProviders);
}
/*******************************************************************************
** Setter for additionalRouteProviders
*******************************************************************************/
public void setAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders)
{
this.additionalRouteProviders = additionalRouteProviders;
}
/*******************************************************************************
** Fluent setter for additionalRouteProviders
*******************************************************************************/
public QApplicationJavalinServer withAdditionalRouteProviders(List<QJavalinRouteProviderInterface> additionalRouteProviders)
{
this.additionalRouteProviders = additionalRouteProviders;
return (this);
}
/*******************************************************************************
** Getter for MILLIS_BETWEEN_HOT_SWAPS
*******************************************************************************/
public long getMillisBetweenHotSwaps()
{
return (millisBetweenHotSwaps);
}
/*******************************************************************************
** Fluent setter for MILLIS_BETWEEN_HOT_SWAPS
*******************************************************************************/
public void withMillisBetweenHotSwaps(long millisBetweenHotSwaps)
{
this.millisBetweenHotSwaps = millisBetweenHotSwaps;
}
/*******************************************************************************
** Getter for hotSwapCustomizer
*******************************************************************************/
public Consumer<QInstance> getHotSwapCustomizer()
{
return (this.hotSwapCustomizer);
}
/*******************************************************************************
** Setter for hotSwapCustomizer
*******************************************************************************/
public void setHotSwapCustomizer(Consumer<QInstance> hotSwapCustomizer)
{
this.hotSwapCustomizer = hotSwapCustomizer;
}
/*******************************************************************************
** Fluent setter for hotSwapCustomizer
*******************************************************************************/
public QApplicationJavalinServer withHotSwapCustomizer(Consumer<QInstance> hotSwapCustomizer)
{
this.hotSwapCustomizer = hotSwapCustomizer;
return (this);
}
/*******************************************************************************
** Getter for javalinConfigurationCustomizer
*******************************************************************************/
public Consumer<Javalin> getJavalinConfigurationCustomizer()
{
return (this.javalinConfigurationCustomizer);
}
/*******************************************************************************
** Setter for javalinConfigurationCustomizer
*******************************************************************************/
public void setJavalinConfigurationCustomizer(Consumer<Javalin> javalinConfigurationCustomizer)
{
this.javalinConfigurationCustomizer = javalinConfigurationCustomizer;
}
/*******************************************************************************
** Fluent setter for javalinConfigurationCustomizer
*******************************************************************************/
public QApplicationJavalinServer withJavalinConfigurationCustomizer(Consumer<Javalin> javalinConfigurationCustomizer)
{
this.javalinConfigurationCustomizer = javalinConfigurationCustomizer;
return (this);
}
}

View File

@ -0,0 +1,47 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import io.javalin.apibuilder.EndpointGroup;
/*******************************************************************************
** Interface for classes that can provide a list of endpoints to a javalin
** server.
*******************************************************************************/
public interface QJavalinRouteProviderInterface
{
/***************************************************************************
** For initial setup when server boots, set the qInstance - but also,
** e.g., for development, to do a hot-swap.
***************************************************************************/
void setQInstance(QInstance qInstance);
/***************************************************************************
**
***************************************************************************/
EndpointGroup getJavalinEndpointGroup();
}

View File

@ -0,0 +1,269 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
import com.kingsrook.qqq.openapi.model.OpenAPI;
import io.javalin.apibuilder.ApiBuilder;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import org.apache.commons.io.IOUtils;
/*******************************************************************************
** javalin-handler that serves both rapidoc static html/css/js files, and
** dynamically generated openapi json/yaml, for a given list of qqq middleware
** versions
*******************************************************************************/
public class QMiddlewareApiSpecHandler
{
private final List<AbstractMiddlewareVersion> middlewareVersionList;
private final String basePath;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QMiddlewareApiSpecHandler(List<AbstractMiddlewareVersion> middlewareVersionList)
{
this(middlewareVersionList, "qqq");
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QMiddlewareApiSpecHandler(List<AbstractMiddlewareVersion> middlewareVersionList, String basePath)
{
this.middlewareVersionList = middlewareVersionList;
this.basePath = basePath.replaceFirst("^/+", "").replaceFirst("/+$", "");;
}
/***************************************************************************
**
***************************************************************************/
public EndpointGroup defineJavalinEndpointGroup()
{
return (() ->
{
ApiBuilder.get("/api/docs/js/rapidoc.min.js", (context) -> serveResource(context, "rapidoc/rapidoc-9.3.8.min.js", MapBuilder.of("Content-Type", ContentType.JAVASCRIPT)));
ApiBuilder.get("/api/docs/css/qqq-api-styles.css", (context) -> serveResource(context, "rapidoc/rapidoc-overrides.css", MapBuilder.of("Content-Type", ContentType.CSS)));
ApiBuilder.get("/images/qqq-api-logo.png", (context) -> serveResource(context, "images/qqq-on-crown-trans-160x80.png", MapBuilder.of("Content-Type", ContentType.IMAGE_PNG.getMimeType())));
//////////////////////////////////////////////
// default page is the current version spec //
//////////////////////////////////////////////
ApiBuilder.get("/" + basePath + "/", context -> doSpecHtml(context));
ApiBuilder.get("/" + basePath + "/versions.json", context -> doVersions(context));
////////////////////////////////////////////
// default page for a version is its spec //
////////////////////////////////////////////
for(AbstractMiddlewareVersion middlewareSpec : middlewareVersionList)
{
String version = middlewareSpec.getVersion();
String versionPath = "/" + basePath + "/" + version;
ApiBuilder.get(versionPath + "/", context -> doSpecHtml(context, version));
///////////////////////////////////////////
// add known paths for specs & docs page //
///////////////////////////////////////////
ApiBuilder.get(versionPath + "/openapi.yaml", context -> doSpecYaml(context, version));
ApiBuilder.get(versionPath + "/openapi.json", context -> doSpecJson(context, version));
ApiBuilder.get(versionPath + "/openapi.html", context -> doSpecHtml(context, version));
}
});
}
/*******************************************************************************
** list the versions in this api
*******************************************************************************/
private void doVersions(Context context)
{
Map<String, Object> rs = new HashMap<>();
List<String> supportedVersions = middlewareVersionList.stream().map(msi -> msi.getVersion()).toList();
String currentVersion = supportedVersions.get(supportedVersions.size() - 1);
rs.put("supportedVersions", supportedVersions);
rs.put("currentVersion", currentVersion);
context.contentType(ContentType.APPLICATION_JSON);
context.result(JsonUtils.toJson(rs));
}
/*******************************************************************************
**
*******************************************************************************/
private void serveResource(Context context, String resourcePath, Map<String, String> headers)
{
InputStream resourceAsStream = QJavalinImplementation.class.getClassLoader().getResourceAsStream(resourcePath);
for(Map.Entry<String, String> entry : CollectionUtils.nonNullMap(headers).entrySet())
{
context.header(entry.getKey(), entry.getValue());
}
context.result(resourceAsStream);
}
/*******************************************************************************
**
*******************************************************************************/
private void doSpecYaml(Context context, String version)
{
try
{
OpenAPI openAPI = new MiddlewareVersionV1().generate(basePath);
context.contentType(ContentType.APPLICATION_YAML);
context.result(YamlUtils.toYaml(openAPI));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void doSpecJson(Context context, String version)
{
try
{
OpenAPI openAPI = new MiddlewareVersionV1().generate(basePath);
context.contentType(ContentType.APPLICATION_JSON);
context.result(JsonUtils.toJson(openAPI));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void doSpecHtml(Context context)
{
String version = null;
try
{
version = context.pathParam("version");
}
catch(Exception e)
{
////////////////
// leave null //
////////////////
}
if(!StringUtils.hasContent(version))
{
List<String> supportedVersions = middlewareVersionList.stream().map(msi -> msi.getVersion()).toList();
version = supportedVersions.get(supportedVersions.size() - 1);
}
doSpecHtml(context, version);
}
/*******************************************************************************
**
*******************************************************************************/
private void doSpecHtml(Context context, String version)
{
try
{
//////////////////////////////////
// read html from resource file //
//////////////////////////////////
InputStream resourceAsStream = QMiddlewareApiSpecHandler.class.getClassLoader().getResourceAsStream("rapidoc/rapidoc-container.html");
String html = IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8);
/////////////////////////////////
// do replacements in the html //
/////////////////////////////////
html = html.replace("{spec-url}", "/" + basePath + "/" + version + "/openapi.json");
html = html.replace("{version}", version);
html = html.replace("{primaryColor}", "#444444");
html = html.replace("{navLogoImg}", "<img id=\"navLogo\" slot=\"nav-logo\" src=\"/images/qqq-api-logo.png\" />");
Optional<AbstractMiddlewareVersion> middlewareSpec = middlewareVersionList.stream().filter(msi -> msi.getVersion().equals(version)).findFirst();
if(middlewareSpec.isEmpty())
{
throw (new QUserFacingException("Unrecognized version: " + version));
}
OpenAPI openAPI = middlewareSpec.get().generate(basePath);
html = html.replace("{title}", openAPI.getInfo().getTitle() + " - " + version);
StringBuilder otherVersionOptions = new StringBuilder();
for(AbstractMiddlewareVersion otherVersionSpec : middlewareVersionList)
{
otherVersionOptions.append("<option value=\"/").append(basePath).append("/").append(otherVersionSpec.getVersion()).append("/openapi.html\">").append(otherVersionSpec.getVersion()).append("</option>");
}
html = html.replace("{otherVersionOptions}", otherVersionOptions.toString());
context.contentType(ContentType.HTML);
context.result(html);
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
}