diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java index f076f682..39090e2f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java @@ -75,7 +75,20 @@ public class PermissionsHelper warnAboutPermissionSubTypeForTables(permissionSubType); QTableMetaData table = QContext.getQInstance().getTable(tableName); - commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName(), actionInput); + commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static String getTablePermissionName(String tableName, TablePermissionSubType permissionSubType) + { + QInstance qInstance = QContext.getQInstance(); + QPermissionRules rules = getEffectivePermissionRules(qInstance.getTable(tableName), qInstance); + String permissionBaseName = getEffectivePermissionBaseName(rules, tableName); + return (getPermissionName(permissionBaseName, permissionSubType)); } @@ -181,7 +194,7 @@ public class PermissionsHelper return; } - commonCheckPermissionThrowing(effectivePermissionRules, PrivatePermissionSubType.HAS_ACCESS, process.getName(), actionInput); + commonCheckPermissionThrowing(effectivePermissionRules, PrivatePermissionSubType.HAS_ACCESS, process.getName()); } @@ -210,7 +223,7 @@ public class PermissionsHelper public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException { QAppMetaData app = QContext.getQInstance().getApp(appName); - commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName(), actionInput); + commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName()); } @@ -239,7 +252,7 @@ public class PermissionsHelper public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException { QReportMetaData report = QContext.getQInstance().getReport(reportName); - commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName(), actionInput); + commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName()); } @@ -268,7 +281,7 @@ public class PermissionsHelper public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException { QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName); - commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName(), actionInput); + commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName()); } @@ -524,7 +537,7 @@ public class PermissionsHelper /******************************************************************************* ** *******************************************************************************/ - private static void commonCheckPermissionThrowing(QPermissionRules rules, PermissionSubType permissionSubType, String name, AbstractActionInput actionInput) throws QPermissionDeniedException + private static void commonCheckPermissionThrowing(QPermissionRules rules, PermissionSubType permissionSubType, String name) throws QPermissionDeniedException { PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType); String permissionBaseName = getEffectivePermissionBaseName(rules, name); diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java index 695f88b7..a3ec4b1a 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java @@ -37,14 +37,19 @@ import com.kingsrook.qqq.api.model.openapi.ExampleWithListValue; import com.kingsrook.qqq.api.model.openapi.ExampleWithSingleValue; import com.kingsrook.qqq.api.model.openapi.Info; import com.kingsrook.qqq.api.model.openapi.Method; +import com.kingsrook.qqq.api.model.openapi.OAuth2; +import com.kingsrook.qqq.api.model.openapi.OAuth2Flow; import com.kingsrook.qqq.api.model.openapi.OpenAPI; import com.kingsrook.qqq.api.model.openapi.Parameter; import com.kingsrook.qqq.api.model.openapi.Path; import com.kingsrook.qqq.api.model.openapi.Response; import com.kingsrook.qqq.api.model.openapi.Schema; +import com.kingsrook.qqq.api.model.openapi.SecurityScheme; import com.kingsrook.qqq.api.model.openapi.Server; import com.kingsrook.qqq.api.model.openapi.Tag; import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -91,11 +96,22 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction()); openAPI.setPaths(new LinkedHashMap<>()); - LinkedHashMap componentResponses = new LinkedHashMap<>(); - LinkedHashMap componentSchemas = new LinkedHashMap<>(); + LinkedHashMap componentResponses = new LinkedHashMap<>(); + LinkedHashMap componentSchemas = new LinkedHashMap<>(); + LinkedHashMap securitySchemes = new LinkedHashMap<>(); openAPI.setComponents(new Components() .withSchemas(componentSchemas) .withResponses(componentResponses) + .withSecuritySchemes(securitySchemes) + ); + + LinkedHashMap scopes = new LinkedHashMap<>(); + securitySchemes.put("OAuth2", new OAuth2() + .withFlows(MapBuilder.of("authorizationCode", new OAuth2Flow() + .withAuthorizationUrl("https://nutrifresh-one-development.us.auth0.com/authorize") + .withTokenUrl("https://nutrifresh-one-development.us.auth0.com/oauth/token") + .withScopes(scopes) + )) ); componentSchemas.put("baseSearchResultFields", new Schema() @@ -131,6 +147,12 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields(); + String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ); + if(StringUtils.hasContent(tableReadPermissionName)) + { + scopes.put(tableReadPermissionName, "Permission to read the " + tableLabel + " table"); + } + //////////////////////// // tag for this table // //////////////////////// @@ -224,7 +246,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction examples; - private Map schemas; - private Map responses; + private Map examples; + private Map schemas; + private Map responses; + private Map securitySchemes; @@ -127,4 +128,35 @@ public class Components return (this); } + + + /******************************************************************************* + ** Getter for securitySchemes + *******************************************************************************/ + public Map getSecuritySchemes() + { + return (this.securitySchemes); + } + + + + /******************************************************************************* + ** Setter for securitySchemes + *******************************************************************************/ + public void setSecuritySchemes(Map securitySchemes) + { + this.securitySchemes = securitySchemes; + } + + + + /******************************************************************************* + ** Fluent setter for securitySchemes + *******************************************************************************/ + public Components withSecuritySchemes(Map securitySchemes) + { + this.securitySchemes = securitySchemes; + return (this); + } + } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Method.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Method.java index c466ef2b..bbea8fe4 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Method.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Method.java @@ -40,6 +40,8 @@ public class Method private RequestBody requestBody; private List parameters; private Map responses; + + private List>> security; @@ -303,4 +305,36 @@ public class Method this.responses.put(code, response); return (this); } + + + + /******************************************************************************* + ** Getter for security + *******************************************************************************/ + public List>> getSecurity() + { + return (this.security); + } + + + + /******************************************************************************* + ** Setter for security + *******************************************************************************/ + public void setSecurity(List>> security) + { + this.security = security; + } + + + + /******************************************************************************* + ** Fluent setter for security + *******************************************************************************/ + public Method withSecurity(List>> security) + { + this.security = security; + return (this); + } + } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2.java new file mode 100644 index 00000000..321eba45 --- /dev/null +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2.java @@ -0,0 +1,77 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.api.model.openapi; + + +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class OAuth2 extends SecurityScheme +{ + private Map flows; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public OAuth2() + { + setType("oauth2"); + } + + + + /******************************************************************************* + ** Getter for flows + *******************************************************************************/ + public Map getFlows() + { + return (this.flows); + } + + + + /******************************************************************************* + ** Setter for flows + *******************************************************************************/ + public void setFlows(Map flows) + { + this.flows = flows; + } + + + + /******************************************************************************* + ** Fluent setter for flows + *******************************************************************************/ + public OAuth2 withFlows(Map flows) + { + this.flows = flows; + return (this); + } + +} diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2Flow.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2Flow.java new file mode 100644 index 00000000..7f8ecac4 --- /dev/null +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/OAuth2Flow.java @@ -0,0 +1,130 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.api.model.openapi; + + +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class OAuth2Flow +{ + private String authorizationUrl; + private String tokenUrl; + private Map scopes; + + + + /******************************************************************************* + ** Getter for authorizationUrl + *******************************************************************************/ + public String getAuthorizationUrl() + { + return (this.authorizationUrl); + } + + + + /******************************************************************************* + ** Setter for authorizationUrl + *******************************************************************************/ + public void setAuthorizationUrl(String authorizationUrl) + { + this.authorizationUrl = authorizationUrl; + } + + + + /******************************************************************************* + ** Fluent setter for authorizationUrl + *******************************************************************************/ + public OAuth2Flow withAuthorizationUrl(String authorizationUrl) + { + this.authorizationUrl = authorizationUrl; + return (this); + } + + + + /******************************************************************************* + ** Getter for tokenUrl + *******************************************************************************/ + public String getTokenUrl() + { + return (this.tokenUrl); + } + + + + /******************************************************************************* + ** Setter for tokenUrl + *******************************************************************************/ + public void setTokenUrl(String tokenUrl) + { + this.tokenUrl = tokenUrl; + } + + + + /******************************************************************************* + ** Fluent setter for tokenUrl + *******************************************************************************/ + public OAuth2Flow withTokenUrl(String tokenUrl) + { + this.tokenUrl = tokenUrl; + return (this); + } + + + + /******************************************************************************* + ** Getter for scopes + *******************************************************************************/ + public Map getScopes() + { + return (this.scopes); + } + + + + /******************************************************************************* + ** Setter for scopes + *******************************************************************************/ + public void setScopes(Map scopes) + { + this.scopes = scopes; + } + + + + /******************************************************************************* + ** Fluent setter for scopes + *******************************************************************************/ + public OAuth2Flow withScopes(Map scopes) + { + this.scopes = scopes; + return (this); + } + +} diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java new file mode 100644 index 00000000..cd2f0c77 --- /dev/null +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java @@ -0,0 +1,63 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.api.model.openapi; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class SecurityScheme +{ + private String type; + + + + /******************************************************************************* + ** Getter for type + *******************************************************************************/ + public String getType() + { + return (this.type); + } + + + + /******************************************************************************* + ** Setter for type + *******************************************************************************/ + public void setType(String type) + { + this.type = type; + } + + + + /******************************************************************************* + ** Fluent setter for type + *******************************************************************************/ + public SecurityScheme withType(String type) + { + this.type = type; + return (this); + } + +} diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/BaseTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/BaseTest.java index 647bf837..2d7fd5b3 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/BaseTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/BaseTest.java @@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -39,6 +40,18 @@ public class BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + @AfterEach + void baseBeforeAndAfterEach() + { + MemoryRecordStore.fullReset(); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java index 19af6bde..b95d7b6e 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/TestUtils.java @@ -22,6 +22,10 @@ package com.kingsrook.qqq.api; +import java.util.List; +import com.kingsrook.qqq.api.model.APIVersion; +import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; +import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat; @@ -39,6 +43,8 @@ public class TestUtils public static final String MEMORY_BACKEND_NAME = "memory"; private static final String TABLE_NAME_PERSON = "person"; + private static final String API_VERSION = "2023.Q1"; + /******************************************************************************* @@ -51,6 +57,11 @@ public class TestUtils qInstance.addBackend(defineMemoryBackend()); qInstance.addTable(defineTablePerson()); + qInstance.withMiddlewareMetaData(new ApiInstanceMetaData() + .withCurrentVersion(new APIVersion(API_VERSION)) + .withSupportedVersions(List.of(new APIVersion(API_VERSION))) + ); + return (qInstance); } @@ -77,6 +88,7 @@ public class TestUtils .withName(TABLE_NAME_PERSON) .withLabel("Person") .withBackendName(MEMORY_BACKEND_NAME) + .withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(API_VERSION)) .withPrimaryKeyField("id") .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java new file mode 100644 index 00000000..504273a4 --- /dev/null +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java @@ -0,0 +1,107 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.api.javalin; + + +import com.kingsrook.qqq.api.TestUtils; +import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + + +/******************************************************************************* + ** Unit test for QJavalinApiHandler + *******************************************************************************/ +class QJavalinApiHandlerTest +{ + private static final int PORT = 6263; + protected static final String BASE_URL = "http://localhost:" + PORT; + + private static final String VERSION = "2023.Q1"; + + protected static QJavalinImplementation qJavalinImplementation; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeAll + static void beforeAll() throws QInstanceValidationException + { + QInstance qInstance = TestUtils.defineInstance(); + qJavalinImplementation = new QJavalinImplementation(qInstance); + qJavalinImplementation.startJavalinServer(PORT); + qJavalinImplementation.getJavalinService().routes(new QJavalinApiHandler(qInstance).getRoutes()); + } + + + + /******************************************************************************* + ** Before the class (all) runs, start a javalin server. + ** + *******************************************************************************/ + @AfterAll + public static void afterAll() + { + qJavalinImplementation.stopJavalinServer(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testSpec() + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/openapi.yaml").asString(); + System.out.println(response.getBody()); + assertThat(response.getBody()) + .contains(""" + title: "QQQ API" + """) + .contains(""" + /person/query: + """) + ; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery() + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString(); + System.out.println(response.getBody()); + } + +} \ No newline at end of file diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 10125be6..4adde09e 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -208,7 +208,7 @@ public class QJavalinImplementation /******************************************************************************* ** *******************************************************************************/ - void startJavalinServer(int port) + public void startJavalinServer(int port) { // todo port from arg // todo base path from arg? - and then potentially multiple instances too (chosen based on the root path??) @@ -221,6 +221,16 @@ public class QJavalinImplementation + /******************************************************************************* + ** + *******************************************************************************/ + public Javalin getJavalinService() + { + return (service); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -277,7 +287,7 @@ public class QJavalinImplementation /******************************************************************************* ** *******************************************************************************/ - void stopJavalinServer() + public void stopJavalinServer() { service.stop(); }