From 11effe0f9146aea805fc068dfdf5fc0f61548575 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 6 Aug 2024 15:44:05 +0700 Subject: [PATCH] feat(Configurations): Add new endpoints that allow to GET/UPDATE SW360 configurations Signed-off-by: hoangnt2 --- .../db/SW360ConfigsDatabaseHandler.java | 126 +++++++++++++ .../eclipse/sw360/spdx/SpdxBOMImporter.java | 7 +- backend/configurations/pom.xml | 37 ++++ .../configurations/SW360ConfigsHandler.java | 49 ++++++ .../configurations/SW360ConfigsServlet.java | 22 +++ .../src/main/webapp/WEB-INF/web.xml | 35 ++++ .../configurations/src/main/webapp/index.jsp | 22 +++ backend/pom.xml | 1 + .../datahandler/common/SW360ConfigKeys.java | 17 ++ .../datahandler/common/SW360Constants.java | 2 - .../sw360/datahandler/common/SW360Utils.java | 9 + .../datahandler/thrift/ThriftClients.java | 6 + .../src/main/thrift/configurations.thrift | 46 +++++ .../datahandler/src/main/thrift/sw360.thrift | 1 + .../src/docs/asciidoc/api-guide.adoc | 1 + .../src/docs/asciidoc/configurations.adoc | 66 +++++++ .../SW360ConfigurationsController.java | 166 ++++++++++++++++++ .../SW360ConfigurationsService.java | 64 +++++++ .../resourceserver/restdocs/ApiSpecTest.java | 1 + .../restdocs/ConfigurationsSpecTest.java | 118 +++++++++++++ 20 files changed, 791 insertions(+), 5 deletions(-) create mode 100644 backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java create mode 100644 backend/configurations/pom.xml create mode 100644 backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsHandler.java create mode 100644 backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsServlet.java create mode 100644 backend/configurations/src/main/webapp/WEB-INF/web.xml create mode 100644 backend/configurations/src/main/webapp/index.jsp create mode 100644 libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java create mode 100644 libraries/datahandler/src/main/thrift/configurations.thrift create mode 100644 rest/resource-server/src/docs/asciidoc/configurations.adoc create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsController.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsService.java create mode 100644 rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ConfigurationsSpecTest.java diff --git a/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java new file mode 100644 index 0000000000..5d81904827 --- /dev/null +++ b/backend/common/src/main/java/org/eclipse/sw360/datahandler/db/SW360ConfigsDatabaseHandler.java @@ -0,0 +1,126 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.datahandler.db; + +import com.ibm.cloud.cloudant.v1.Cloudant; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; +import org.eclipse.sw360.datahandler.common.SW360ConfigKeys; +import org.eclipse.sw360.datahandler.permissions.PermissionUtils; +import org.eclipse.sw360.datahandler.thrift.ConfigContainer; +import org.eclipse.sw360.datahandler.thrift.ConfigFor; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.users.User; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Collections; + +public class SW360ConfigsDatabaseHandler { + private final Logger log = LogManager.getLogger(this.getClass()); + private final ConfigContainerRepository repository; + private static final Map configsMapInMem = new HashMap<>(); + private static boolean updating = false; + + public SW360ConfigsDatabaseHandler(Cloudant httpClient, String dbName) { + DatabaseConnectorCloudant db = new DatabaseConnectorCloudant(httpClient, dbName); + repository = new ConfigContainerRepository(db); + try { + loadToConfigsMapInMem(repository.getByConfigFor(ConfigFor.SW360_CONFIGURATION)); + } catch (IllegalStateException exception) { + log.error(exception.getMessage()); + loadToConfigsMapInMem(null); + } + } + + private void loadToConfigsMapInMem(ConfigContainer configContainer) { + configsMapInMem + .put(SW360ConfigKeys.SPDX_DOCUMENT_ENABLED, getOrDefault(configContainer, SW360ConfigKeys.SPDX_DOCUMENT_ENABLED, "false")); + } + + private String getOrDefault(ConfigContainer configContainer, String configKey, String defaultValue) { + if (configContainer == null) + return defaultValue; + return configContainer.getConfigKeyToValues().getOrDefault(configKey, new HashSet<>()).stream().findFirst().orElse(defaultValue); + } + + private boolean isBooleanValue(String value) { + return (value != null) && (value.equals("true") || value.equals("false")); + } + + private boolean isConfigValid(String configKey, String configValue) { + switch (configKey) { + case SW360ConfigKeys.SPDX_DOCUMENT_ENABLED: + return isBooleanValue(configValue); + default: + return false; + } + } + + private void updateExistingConfigs(Map> existingConfigs, Map updatedConfigs) throws SW360Exception { + for (Map.Entry config : updatedConfigs.entrySet()) { + if (!isConfigValid(config.getKey(), config.getValue())) { + updating = false; + throw new SW360Exception("Invalid config: [" + config.getKey() + " : " + config.getValue() + "]"); + } + existingConfigs.put(config.getKey(), Collections.singleton(config.getValue())); + } + } + + public RequestStatus updateSW360Configs(Map updatedConfigs, User user) throws SW360Exception { + if (!PermissionUtils.isAdmin(user)) + return RequestStatus.ACCESS_DENIED; + + if (updating) { + return RequestStatus.IN_USE; + } + + updating = true; + + if (updatedConfigs == null || updatedConfigs.isEmpty()) { + updating = false; + return RequestStatus.SUCCESS; + } + + ConfigContainer configContainer; + try { + configContainer = repository.getByConfigFor(ConfigFor.SW360_CONFIGURATION); + } catch (IllegalStateException exception) { + log.error(exception.getMessage()); + configContainer = new ConfigContainer(ConfigFor.SW360_CONFIGURATION, new HashMap<>()); + } + updateExistingConfigs(configContainer.getConfigKeyToValues(), updatedConfigs); + + if (configContainer.getId() != null) { + repository.update(configContainer); + } else { + repository.add(configContainer); + } + + loadToConfigsMapInMem(configContainer); + updating = false; + return RequestStatus.SUCCESS; + } + + + public Map getSW360Configs() { + return configsMapInMem; + } + + public String getConfigByKey(String key) { + return configsMapInMem.get(key); + } +} diff --git a/backend/common/src/main/java/org/eclipse/sw360/spdx/SpdxBOMImporter.java b/backend/common/src/main/java/org/eclipse/sw360/spdx/SpdxBOMImporter.java index 7b9d4ac4a2..c247149df6 100644 --- a/backend/common/src/main/java/org/eclipse/sw360/spdx/SpdxBOMImporter.java +++ b/backend/common/src/main/java/org/eclipse/sw360/spdx/SpdxBOMImporter.java @@ -60,6 +60,7 @@ import static org.eclipse.sw360.datahandler.common.CommonUtils.isNotNullEmptyOrWhitespace; import static org.eclipse.sw360.datahandler.common.CommonUtils.isNullEmptyOrWhitespace; +import static org.eclipse.sw360.datahandler.common.SW360ConfigKeys.SPDX_DOCUMENT_ENABLED; public class SpdxBOMImporter { private static final Logger log = LogManager.getLogger(SpdxBOMImporter.class); @@ -205,6 +206,7 @@ private RequestSummary importSpdxBOM(InputStream inputStream, AttachmentContent } catch (InvalidSPDXAnalysisException | NullPointerException e) { log.error("Can not open file to SpdxDocument " +e); } + return requestSummary; } @@ -751,7 +753,7 @@ private Optional importAsRelease(SpdxElement relat spdxPackages.add(spdxPackageCheck); } importAsReleaseFromSpdxDocument(spdxPackages,attachmentContent,spdxDocument); - if (SW360Constants.SPDX_DOCUMENT_ENABLED) { + if (Boolean.parseBoolean(SW360Utils.getConfigByKey(SPDX_DOCUMENT_ENABLED))) { try { importSpdxDocument(response.getId(), spdxDocument, spdxPackage); } catch (MalformedURLException e) { @@ -799,7 +801,7 @@ private void importAsReleaseFromSpdxDocument(List packages, Attach release.setAttachments(Collections.singleton(attachment)); } final SpdxBOMImporterSink.Response response = sink.addRelease(release); - if (SW360Constants.SPDX_DOCUMENT_ENABLED) { + if (Boolean.parseBoolean(SW360Utils.getConfigByKey(SPDX_DOCUMENT_ENABLED))) { try { importSpdxDocument(response.getId(), spdxDocument, spdxElement); } catch (MalformedURLException e) { @@ -938,7 +940,6 @@ private Map makeReleaseIdToProjectRelationsh })); } - private Optional importAsProject(SpdxElement spdxElement, AttachmentContent attachmentContent, User user) throws SW360Exception, InvalidSPDXAnalysisException { if (spdxElement instanceof SpdxPackage) { final SpdxPackage spdxPackage = (SpdxPackage) spdxElement; diff --git a/backend/configurations/pom.xml b/backend/configurations/pom.xml new file mode 100644 index 0000000000..104c23445b --- /dev/null +++ b/backend/configurations/pom.xml @@ -0,0 +1,37 @@ + + + + 4.0.0 + + org.eclipse.sw360 + backend + 19.0.0 + + + backend-configurations + war + backend-configurations + configurations + + ${backend.deploy.dir} + + + + + org.eclipse.sw360 + backend-common + ${project.version} + + + \ No newline at end of file diff --git a/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsHandler.java b/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsHandler.java new file mode 100644 index 0000000000..a1de3e14f4 --- /dev/null +++ b/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.configurations; + +import org.eclipse.sw360.datahandler.common.DatabaseSettings; +import org.eclipse.sw360.datahandler.db.SW360ConfigsDatabaseHandler; +import org.eclipse.sw360.datahandler.thrift.ConfigContainer; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.configurations.SW360ConfigsService; +import org.eclipse.sw360.datahandler.thrift.users.User; +import java.util.Map; + +public class SW360ConfigsHandler implements SW360ConfigsService.Iface { + private final SW360ConfigsDatabaseHandler sw360ConfigsDatabaseHandler; + + public SW360ConfigsHandler() { + sw360ConfigsDatabaseHandler = new SW360ConfigsDatabaseHandler(DatabaseSettings.getConfiguredClient(), DatabaseSettings.COUCH_DB_CONFIG); + } + + @Override + public RequestStatus createSW360Configs(ConfigContainer newConfig) { + return RequestStatus.SUCCESS; + } + + @Override + public RequestStatus updateSW360Configs(Map updatedConfigs, User user) throws SW360Exception { + return sw360ConfigsDatabaseHandler.updateSW360Configs(updatedConfigs, user); + } + + @Override + public Map getSW360Configs() { + return sw360ConfigsDatabaseHandler.getSW360Configs(); + } + + @Override + public String getConfigByKey(String key) { + return sw360ConfigsDatabaseHandler.getConfigByKey(key); + } +} diff --git a/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsServlet.java b/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsServlet.java new file mode 100644 index 0000000000..d1f0f9b693 --- /dev/null +++ b/backend/configurations/src/main/java/org/eclipse/sw360/configurations/SW360ConfigsServlet.java @@ -0,0 +1,22 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.configurations; + +import org.apache.thrift.protocol.TCompactProtocol; +import org.eclipse.sw360.datahandler.thrift.configurations.SW360ConfigsService; +import org.eclipse.sw360.projects.Sw360ThriftServlet; + +public class SW360ConfigsServlet extends Sw360ThriftServlet { + public SW360ConfigsServlet() { + super(new SW360ConfigsService.Processor<>(new SW360ConfigsHandler()), new TCompactProtocol.Factory()); + } +} diff --git a/backend/configurations/src/main/webapp/WEB-INF/web.xml b/backend/configurations/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..2532b8e39a --- /dev/null +++ b/backend/configurations/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,35 @@ + + + + SW360 Configurations Service + + index.jsp + + + + org.eclipse.sw360.SW360ServiceContextListener + + + + SW360ConfigsService + org.eclipse.sw360.configurations.SW360ConfigsServlet + 1 + + + SW360ConfigsService + /thrift + + + \ No newline at end of file diff --git a/backend/configurations/src/main/webapp/index.jsp b/backend/configurations/src/main/webapp/index.jsp new file mode 100644 index 0000000000..f4a192ca09 --- /dev/null +++ b/backend/configurations/src/main/webapp/index.jsp @@ -0,0 +1,22 @@ + + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Welcome to the SW360 Configurations Service + + + +

Welcome to the SW360 Configurations Service!

+ + diff --git a/backend/pom.xml b/backend/pom.xml index b817aa8767..7b9838c80d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -28,6 +28,7 @@ attachments components cvesearch + configurations fossology health licenseinfo diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java new file mode 100644 index 0000000000..2e7aed7c59 --- /dev/null +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360ConfigKeys.java @@ -0,0 +1,17 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.datahandler.common; + +public class SW360ConfigKeys { + public static final String SPDX_DOCUMENT_ENABLED = "spdx.document.enabled"; + public static final String ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP = "enable.flexible.project.release.relationship"; +} diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Constants.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Constants.java index df1dc36f0d..9f86b465e2 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Constants.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Constants.java @@ -108,7 +108,6 @@ public class SW360Constants { public static final String TOTAL_FILE_COUNT = "totalFileCount"; public static final String SVM_COMPONENT_ID; public static final String SVM_MONITORINGLIST_ID; - public static final Boolean SPDX_DOCUMENT_ENABLED; public static final String MAINLINE_COMPONENT_ID; public static final String SVM_COMPONENT_ID_KEY; public static final String SVM_SHORT_STATUS; @@ -231,7 +230,6 @@ private SW360Constants() { SVM_SHORT_STATUS_KEY = props.getProperty("svm.short.status.key", ""); SVM_SCHEDULER_EMAIL = props.getProperty("svm.scheduler.email", ""); SVM_MONITORINGLIST_ID = props.getProperty("svm.monitoringlist.id", ""); - SPDX_DOCUMENT_ENABLED = Boolean.parseBoolean(props.getProperty("spdx.document.enabled", "false")); DATA_HANDLER_POM_FILE_PATH = props.getProperty("datahandler.pom.file.path", "/META-INF/maven/org.eclipse.sw360/datahandler/pom.xml"); PACKAGE_PORTLET_WRITE_ACCESS_USER_ROLE = UserGroup.valueOf(props.getProperty("package.portlet.write.access.usergroup", UserGroup.USER.name())); IS_PACKAGE_PORTLET_ENABLED = Boolean.parseBoolean(props.getProperty("package.portlet.enabled", "true")); diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Utils.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Utils.java index 35b7826394..8ffe4d2860 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Utils.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/common/SW360Utils.java @@ -33,6 +33,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.components.*; +import org.eclipse.sw360.datahandler.thrift.configurations.SW360ConfigsService; import org.eclipse.sw360.datahandler.thrift.licenses.License; import org.eclipse.sw360.datahandler.thrift.licenses.LicenseService; import org.eclipse.sw360.datahandler.thrift.licenses.ObligationLevel; @@ -1073,4 +1074,12 @@ public static Collection getLinkedProjectsWithAllReleases(Project p return Collections.emptyList(); } + public static String getConfigByKey(String key) throws SW360Exception { + try { + SW360ConfigsService.Iface configClient = new ThriftClients().makeSW360ConfigsClient(); + return configClient.getConfigByKey(key); + } catch (TException exception) { + throw new SW360Exception("Unable to get configuration"); + } + } } diff --git a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftClients.java b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftClients.java index eb25b413de..7eb046abff 100644 --- a/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftClients.java +++ b/libraries/datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftClients.java @@ -27,6 +27,7 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentService; import org.eclipse.sw360.datahandler.thrift.changelogs.ChangeLogsService; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; +import org.eclipse.sw360.datahandler.thrift.configurations.SW360ConfigsService; import org.eclipse.sw360.datahandler.thrift.cvesearch.CveSearchService; import org.eclipse.sw360.datahandler.thrift.fossology.FossologyService; import org.eclipse.sw360.datahandler.thrift.health.HealthService; @@ -94,6 +95,7 @@ public class ThriftClients { private static final String SPDX_PACKAGE_INFO_SERVICE_URL = "/spdxpackageinfo/thrift"; private static final String SPDX_FILE_INFO_SERVICE_URL = "/fileinformation/thrift"; private static final String PACKAGE_SERVICE_URL = "/packages/thrift"; + private static final String SW360_CONFIGS_SERVICE_URL = "/configurations/thrift"; // A service which has to be scheduled by the scheduler should be registered here! // names of services that can be scheduled by the schedule service, i.e. that have an "update" method @@ -249,4 +251,8 @@ public FileInformationService.Iface makeSPDXFileInfoClient() { public PackageService.Iface makePackageClient() { return new PackageService.Client(makeProtocol(BACKEND_URL, PACKAGE_SERVICE_URL)); } + + public SW360ConfigsService.Iface makeSW360ConfigsClient() { + return new SW360ConfigsService.Client(makeProtocol(BACKEND_URL, SW360_CONFIGS_SERVICE_URL)); + } } diff --git a/libraries/datahandler/src/main/thrift/configurations.thrift b/libraries/datahandler/src/main/thrift/configurations.thrift new file mode 100644 index 0000000000..3a797f6aee --- /dev/null +++ b/libraries/datahandler/src/main/thrift/configurations.thrift @@ -0,0 +1,46 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +include "sw360.thrift" +include "users.thrift" + +namespace java org.eclipse.sw360.datahandler.thrift.configurations +namespace php sw360.thrift.configurations + +typedef sw360.SW360Exception SW360Exception +typedef sw360.RequestStatus RequestStatus +typedef sw360.ConfigContainer ConfigContainer +typedef users.User User + +service SW360ConfigsService { + + /** + * Create ConfigContainer with configFor SW360_CONFIGURATION. + * Only users have role ADMIN are allowed + **/ + RequestStatus createSW360Configs(1: ConfigContainer newConfig); + + /** + * Update config in database with configFor SW360_CONFIGURATION. + * Only users have role ADMIN are allowed + **/ + RequestStatus updateSW360Configs(1: map updatedConfigs, 2: User user) throws (1: SW360Exception exp); + + /** + * gets the current sw360 config map for configFor SW360_CONFIGURATION. + **/ + map getSW360Configs(); + + /** + * get config value by config key name. + **/ + string getConfigByKey(string key); + +} diff --git a/libraries/datahandler/src/main/thrift/sw360.thrift b/libraries/datahandler/src/main/thrift/sw360.thrift index 752184ad92..0a3a760746 100644 --- a/libraries/datahandler/src/main/thrift/sw360.thrift +++ b/libraries/datahandler/src/main/thrift/sw360.thrift @@ -134,6 +134,7 @@ enum MainlineState { enum ConfigFor { FOSSOLOGY_REST = 0, + SW360_CONFIGURATION = 1, } enum ObligationStatus { diff --git a/rest/resource-server/src/docs/asciidoc/api-guide.adoc b/rest/resource-server/src/docs/asciidoc/api-guide.adoc index 17fafca60d..274e9ba903 100644 --- a/rest/resource-server/src/docs/asciidoc/api-guide.adoc +++ b/rest/resource-server/src/docs/asciidoc/api-guide.adoc @@ -311,3 +311,4 @@ include::ecc.adoc[] include::importExport.adoc[] include::attachmentCleanUp.adoc[] include::databaseSanitation.adoc[] +include::configurations.adoc[] diff --git a/rest/resource-server/src/docs/asciidoc/configurations.adoc b/rest/resource-server/src/docs/asciidoc/configurations.adoc new file mode 100644 index 0000000000..5c4d99179a --- /dev/null +++ b/rest/resource-server/src/docs/asciidoc/configurations.adoc @@ -0,0 +1,66 @@ +// +// Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. +// Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. +// +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// + +[[resources-configurations]] +=== Configurations + +The configurations resource is used to list SW360 configurations information. + + +[[resources-configurations-list]] +==== Listing configurations + +A `GET` request will list all configurations of SW360. + +===== Example request +include::{snippets}/should_document_get_all_configurations/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_all_configurations/http-response.adoc[] + +[[resources-changeable-configurations-list]] +==== Listing changeable configurations + +A `GET` request will list changeable configurations of SW360 (stored in database). + +===== Request parameter +include::{snippets}/should_document_get_changeable_configurations/query-parameters.adoc[] + +===== Example request +include::{snippets}/should_document_get_changeable_configurations/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_changeable_configurations/http-response.adoc[] + +[[resources-not-changeable-configurations-list]] +==== List of configurations can not be changed + +A `GET` request will list configurations that cannot be changed (stored in properties file, cannot update). + +===== Request parameter +include::{snippets}/should_document_get_changeable_configurations/query-parameters.adoc[] + +===== Example request +include::{snippets}/should_document_get_not_changeable_configurations/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_not_changeable_configurations/http-response.adoc[] + +[[resources-update-changeable-configurations]] +==== Update changeable configurations + +A `PATCH` request update changeable configurations (stored in database). + +===== Example request +include::{snippets}/should_document_update_changeable_configurations/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_update_changeable_configurations/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsController.java new file mode 100644 index 0000000000..c984193720 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsController.java @@ -0,0 +1,166 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.rest.resourceserver.configuration; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.BasePathAwareController; +import org.springframework.data.rest.webmvc.RepositoryLinksResource; +import org.springframework.hateoas.server.RepresentationModelProcessor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.InvalidPropertiesFormatException; +import java.util.Map; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +@BasePathAwareController +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +@RestController +@SecurityRequirement(name = "tokenAuth") +@SecurityRequirement(name = "basic") +@Tag(name = "SW360 Configurations", description = "Operations related to configurations") +public class SW360ConfigurationsController implements RepresentationModelProcessor { + private static final String SW360_CONFIG_URL = "/configurations"; + + @NonNull + private final RestControllerHelper restControllerHelper; + + @NonNull + SW360ConfigurationsService sw360ConfigurationsService; + + @Override + public RepositoryLinksResource process(RepositoryLinksResource resource) { + resource.add(linkTo(SW360ConfigurationsController.class) + .slash("api" + SW360_CONFIG_URL).withRel("configurations")); + return resource; + } + + @GetMapping(value = SW360_CONFIG_URL) + @Operation(summary = "Get configurations", + description = "This method lists SW360 configurations") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "List configurations successful", + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + schema = @Schema(implementation = Map.class), + examples = @ExampleObject( + value = """ + { + "sw360.tool.name" : "SW360", + "spdx.document.enabled" : "true" + } + """ + ) + ) + ) + }) + public ResponseEntity getSW360Configurations( + @Parameter( + description = "Filter changeable (true) or not changeable (false) configurations. By default list all.", + required = false, + schema = @Schema(type = "boolean", example = "true") + ) + @RequestParam(required = false, name = "changeable") Boolean changeable) + throws TException { + if (changeable == null) { + return ResponseEntity.ok(sw360ConfigurationsService.getSW360Configs()); + } + + if (changeable) { + return ResponseEntity.ok(sw360ConfigurationsService.getSW360ConfigFromDb()); + } + + return ResponseEntity.ok(sw360ConfigurationsService.getSW360ConfigFromProperties()); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @PatchMapping(value = SW360_CONFIG_URL, consumes = {MediaType.APPLICATION_JSON_VALUE}) + @Operation(summary = "Get configurations", + description = "This method lists SW360 configurations") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Update configurations successful", + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + examples = @ExampleObject( + value = "Configurations are updated successfully" + ) + ) + ), + @ApiResponse(responseCode = "403", description = "Only ADMIN users are allowed to update configurations", + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + examples = @ExampleObject( + value = """ + { + "timestamp": "2024-08-29T03:10:41.486227465Z", + "status": 403, + "error": "Forbidden", + "message": "Access Denied" + } + """ + ) + ) + ), + @ApiResponse(responseCode = "409", description = "Configurations are being updated by another administrator", + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + examples = @ExampleObject( + value = "Configurations are being updated by another administrator, please try again later" + ) + ) + ), + @ApiResponse(responseCode = "400", description = "Invalid configurations", + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + examples = @ExampleObject( + value = "Invalid config: [enable.flexible.project.release.relationship : false]" + ) + ) + ) + }) + public ResponseEntity updateSW360Configurations(@RequestBody Map configuration) + throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + try { + RequestStatus updateStatus = sw360ConfigurationsService.updateSW360Configs(configuration, sw360User); + if (updateStatus.equals(RequestStatus.IN_USE)) { + return new ResponseEntity<>("Configurations are being updated by another administrator, please try again later", HttpStatus.CONFLICT); + } + } catch (InvalidPropertiesFormatException invalidPropertiesFormatException) { + return new ResponseEntity<>(invalidPropertiesFormatException.getMessage(), HttpStatus.BAD_REQUEST); + } + return ResponseEntity.ok("Configurations are updated successfully"); + } + +} diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsService.java new file mode 100644 index 0000000000..188fa7654d --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/configuration/SW360ConfigurationsService.java @@ -0,0 +1,64 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.rest.resourceserver.configuration; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.common.SW360ConfigKeys; +import org.eclipse.sw360.datahandler.common.SW360Constants; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.ThriftClients; +import org.eclipse.sw360.datahandler.thrift.configurations.SW360ConfigsService; +import org.eclipse.sw360.datahandler.thrift.users.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.InvalidPropertiesFormatException; +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SW360ConfigurationsService { + private SW360ConfigsService.Iface getThriftConfigsClient() { + return new ThriftClients().makeSW360ConfigsClient(); + } + + public Map getSW360Configs() throws TException { + Map combinedConfig = getSW360ConfigFromDb(); + combinedConfig.putAll(getSW360ConfigFromProperties()); + return combinedConfig; + } + + public Map getSW360ConfigFromDb() throws TException { + SW360ConfigsService.Iface configService = getThriftConfigsClient(); + return configService.getSW360Configs(); + } + + public Map getSW360ConfigFromProperties() { + Map configFromProperties = new HashMap<>(); + configFromProperties.put(SW360ConfigKeys.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP, String.valueOf(SW360Constants.ENABLE_FLEXIBLE_PROJECT_RELEASE_RELATIONSHIP)); + return configFromProperties; + } + + public RequestStatus updateSW360Configs(Map updatedConfig, User user) throws TException, InvalidPropertiesFormatException { + try { + SW360ConfigsService.Iface configsService = getThriftConfigsClient(); + return configsService.updateSW360Configs(updatedConfig, user); + } catch (SW360Exception sw360Exception) { + throw new InvalidPropertiesFormatException(sw360Exception.getWhy()); + } + } +} diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ApiSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ApiSpecTest.java index a15b6423c2..8e6dea35bb 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ApiSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ApiSpecTest.java @@ -176,6 +176,7 @@ public void should_document_index() throws Exception { linkWithRel("sw360:ecc").description("The <>"), linkWithRel("sw360:attachmentCleanUp").description("The <>"), linkWithRel("sw360:importExport").description("The <>"), + linkWithRel("sw360:configurations").description("The <>"), linkWithRel("curies").description("The Curies for documentation"), linkWithRel("profile").description("The profiles of the REST resources") ), diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ConfigurationsSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ConfigurationsSpecTest.java new file mode 100644 index 0000000000..8478c9dfa8 --- /dev/null +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ConfigurationsSpecTest.java @@ -0,0 +1,118 @@ +/* + * Copyright TOSHIBA CORPORATION, 2024. Part of the SW360 Portal Project. + * Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2024. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.rest.resourceserver.restdocs; + +import org.apache.thrift.TException; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; +import org.eclipse.sw360.rest.resourceserver.TestHelper; +import org.eclipse.sw360.rest.resourceserver.configuration.SW360ConfigurationsService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringJUnit4ClassRunner.class) +public class ConfigurationsSpecTest extends TestRestDocsSpecBase { + @Value("${sw360.test-user-id}") + private String testUserId; + + @Value("${sw360.test-user-password}") + private String testUserPassword; + + @MockBean + private SW360ConfigurationsService sw360ConfigurationsService; + + @Before + public void before() throws TException, IOException { + Map configsFromProperties = Map.of( + "enable.flexible.project.release.relationship", "true", + "svm.component.id", "svm_component_id" + ); + + Map configsFromDb = Map.of( + "spdx.document.enabled", "true", + "sw360.tool.name", "SW360" + ); + + Map allConfigs = new HashMap<>(); + allConfigs.putAll(configsFromProperties); + allConfigs.putAll(configsFromDb); + + given(sw360ConfigurationsService.getSW360ConfigFromProperties()).willReturn(configsFromProperties); + given(sw360ConfigurationsService.getSW360ConfigFromDb()).willReturn(configsFromDb); + given(sw360ConfigurationsService.getSW360Configs()).willReturn(allConfigs); + given(sw360ConfigurationsService.updateSW360Configs(any(), any())).willReturn(RequestStatus.SUCCESS); + } + + @Test + public void should_document_get_all_configurations() throws Exception { + mockMvc.perform(get("/api/configurations") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()); + } + + @Test + public void should_document_get_changeable_configurations() throws Exception { + mockMvc.perform(get("/api/configurations") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .queryParam("changeable", "true") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + queryParameters( + parameterWithName("changeable") + .description("Filter changeable (true) or not changeable (false) configuration. By default lists all") + ) + )); + } + + @Test + public void should_document_get_not_changeable_configurations() throws Exception { + mockMvc.perform(get("/api/configurations") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .queryParam("changeable", "false") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + queryParameters( + parameterWithName("changeable") + .description("Filter changeable (true) or not changeable (false) configuration. By default lists all") + ) + )); + } + + @Test + public void should_document_update_changeable_configurations() throws Exception { + Map updatedConfigurations = Map.of("spdx.document.enabled", "false"); + mockMvc.perform(patch("/api/configurations") + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(this.objectMapper.writeValueAsString(updatedConfigurations)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()); + } +}