From d0eae15ce508fb65f718ef7167aa3cfca698a8e8 Mon Sep 17 00:00:00 2001 From: "antoine.vinot" Date: Tue, 30 Apr 2024 12:05:46 +0200 Subject: [PATCH] SONAR-22034 New Endpoints to get JREs metadata and download JRE --- server/sonar-webserver-webapi-v2/build.gradle | 1 + .../org/sonar/server/v2/WebApiEndpoints.java | 1 + .../controller/DefaultJresController.java | 51 ++++++ .../analysis/controller/JresController.java | 67 ++++++++ .../response/JreInfoRestResponse.java | 30 ++++ .../api/analysis/response/package-info.java | 23 +++ .../v2/api/analysis/service/JresHandler.java | 33 ++++ .../api/analysis/service/JresHandlerImpl.java | 148 ++++++++++++++++++ .../v2/api/analysis/service/package-info.java | 23 +++ .../v2/config/PlatformLevel4WebConfig.java | 14 ++ .../controller/DefaultJresControllerTest.java | 132 ++++++++++++++++ .../DefaultVersionControllerTest.java | 6 +- .../analysis/service/JresHandlerImplTest.java | 130 +++++++++++++++ .../config/PlatformLevel4WebConfigTest.java | 27 +++- .../test/resources/jres-metadata-tests.json | 22 +++ 15 files changed, 699 insertions(+), 9 deletions(-) create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultJresController.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/JresController.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/JreInfoRestResponse.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/package-info.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandler.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandlerImpl.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/package-info.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultJresControllerTest.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/JresHandlerImplTest.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/resources/jres-metadata-tests.json diff --git a/server/sonar-webserver-webapi-v2/build.gradle b/server/sonar-webserver-webapi-v2/build.gradle index 760f4a25198..2acb3187201 100644 --- a/server/sonar-webserver-webapi-v2/build.gradle +++ b/server/sonar-webserver-webapi-v2/build.gradle @@ -17,6 +17,7 @@ dependencies { testImplementation 'javax.servlet:javax.servlet-api' testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.skyscreamer:jsonassert:1.5.1' diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java index 336ae93ad2e..9ba51c1c17a 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java @@ -48,6 +48,7 @@ public class WebApiEndpoints { public static final String ANALYSIS_ENDPOINT = "/analysis"; public static final String VERSION_ENDPOINT = ANALYSIS_ENDPOINT + "/version"; + public static final String JRE_ENDPOINT = ANALYSIS_ENDPOINT + "/jres"; private WebApiEndpoints() { } diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultJresController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultJresController.java new file mode 100644 index 00000000000..da3e643ce87 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultJresController.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.controller; + +import java.util.List; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; +import org.sonar.server.v2.api.analysis.service.JresHandler; +import org.springframework.core.io.InputStreamResource; + +public class DefaultJresController implements JresController { + + private final JresHandler jresHandler; + + public DefaultJresController(JresHandler jresHandler) { + this.jresHandler = jresHandler; + } + + @Override + public List getJresMetadata(String os, String arch) { + return jresHandler.getJresMetadata(os, arch); + } + + @Override + public JreInfoRestResponse getJreMetadata(String id) { + return jresHandler.getJreMetadata(id); + } + + @Override + public InputStreamResource downloadJre(String id) { + JreInfoRestResponse jreInfoRestResponse = jresHandler.getJreMetadata(id); + return new InputStreamResource(jresHandler.getJreBinary(jreInfoRestResponse.filename())); + } + +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/JresController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/JresController.java new file mode 100644 index 00000000000..7e18f63bc1a --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/JresController.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import java.util.List; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; +import org.springframework.core.io.InputStreamResource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; +import static org.sonar.server.v2.WebApiEndpoints.JRE_ENDPOINT; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; + +@RequestMapping(value = JRE_ENDPOINT, produces = APPLICATION_JSON_VALUE) +@ResponseStatus(OK) +@RestController +public interface JresController { + + String OS_PARAM_DESCRIPTION = "Filter the JRE by operating system. Accepted values are 'windows', 'linux', 'macos', 'alpine' (case-insensitive), with some aliases"; + String ARCH_PARAM_DESCRIPTION = "Filter the JRE by CPU architecture. Accepted values are 'x64' and 'aarch64' (case-insensitive), with some aliases."; + String GET_JRE_DESCRIPTION = + "This endpoint return the JRE metadata by default. To download the JRE binary asset, set the Accept header of the request to 'application/octet-stream'."; + String GET_JRE_SUMMARY = "JRE download/metadata"; + String ID_PARAM_DESCRIPTION = "The ID of the JRE"; + + @GetMapping + @Operation(summary = "All JREs metadata", description = "Get metadata of all available JREs") + List getJresMetadata( + @RequestParam(value = "os", required = false) @Parameter(description = OS_PARAM_DESCRIPTION) String os, + @RequestParam(value = "arch", required = false) @Parameter(description = ARCH_PARAM_DESCRIPTION) String arch); + + @GetMapping(value = "/{id}") + @Operation(summary = GET_JRE_SUMMARY, description = GET_JRE_DESCRIPTION) + JreInfoRestResponse getJreMetadata( + @PathVariable(value = "id") @Parameter(description = ID_PARAM_DESCRIPTION, required = true, in = PATH) String id); + + @GetMapping(value = "/{id}", produces = APPLICATION_OCTET_STREAM_VALUE) + @Operation(summary = GET_JRE_SUMMARY, description = GET_JRE_DESCRIPTION) + InputStreamResource downloadJre( + @PathVariable(value = "id") @Parameter(description = ID_PARAM_DESCRIPTION, required = true, in = PATH) String id); +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/JreInfoRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/JreInfoRestResponse.java new file mode 100644 index 00000000000..63f1853b259 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/JreInfoRestResponse.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.response; + +public record JreInfoRestResponse( + String id, + String filename, + String sha256, + String javaPath, + String os, + String arch +) { +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/package-info.java new file mode 100644 index 00000000000..3f469e853a8 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.v2.api.analysis.response; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandler.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandler.java new file mode 100644 index 00000000000..3f4f56e02fb --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandler.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.service; + +import java.io.InputStream; +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; + +public interface JresHandler { + List getJresMetadata(@Nullable String os, @Nullable String arch); + + JreInfoRestResponse getJreMetadata(String id); + + InputStream getJreBinary(String jreFilename); +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandlerImpl.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandlerImpl.java new file mode 100644 index 00000000000..53a06aaf4e0 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandlerImpl.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; + +import static java.lang.String.join; +import static org.apache.commons.lang.StringUtils.isBlank; + +public class JresHandlerImpl implements JresHandler { + + private static final String JRES_METADATA_FILENAME = "jres-metadata.json"; + + private final String jresMetadataFilename; + private final Map metadata = new HashMap<>(); + + public JresHandlerImpl() { + this(JRES_METADATA_FILENAME); + } + + @VisibleForTesting + public JresHandlerImpl(String jresMetadataFilename) { + this.jresMetadataFilename = jresMetadataFilename; + } + + @PostConstruct + void initMetadata() { + metadata.clear(); + readJresMetadata().forEach(jre -> metadata.put(jre.id(), jre)); + } + + private List readJresMetadata() { + ObjectMapper objectMapper = new ObjectMapper(); + try (InputStream is = getClass().getClassLoader().getResourceAsStream(jresMetadataFilename)) { + return objectMapper.readValue(is, objectMapper.getTypeFactory().constructCollectionType(List.class, JreInfoRestResponse.class)); + } catch (IOException ioException) { + throw new UncheckedIOException(ioException); + } + } + + @Override + public List getJresMetadata(@Nullable String os, @Nullable String arch) { + Predicate osFilter = isBlank(os) ? jre -> true : (jre -> OS.from(jre.os()) == OS.from(os)); + Predicate archFilter = isBlank(arch) ? jre -> true : (jre -> Arch.from(jre.arch()) == Arch.from(arch)); + return metadata.values().stream() + .filter(osFilter) + .filter(archFilter) + .toList(); + } + + @Override + public JreInfoRestResponse getJreMetadata(String id) { + return Optional.ofNullable(metadata.get(id)) + .orElseThrow(() -> new NotFoundException("JRE not found for id: " + id)); + } + + @Override + public InputStream getJreBinary(String jreFilename) { + try { + return new FileInputStream("jres/" + jreFilename); + } catch (FileNotFoundException fileNotFoundException) { + throw new NotFoundException(String.format("Unable to find JRE '%s'", jreFilename)); + } + } + + enum OS { + WINDOWS("win", "windows", "win32"), + LINUX("linux"), + MACOS("mac", "macos", "darwin"), + ALPINE("alpine"); + + private final List aliases; + + OS(String... aliases) { + this.aliases = Arrays.stream(aliases).toList(); + } + + private static OS from(String alias) { + return Arrays.stream(values()) + .filter(os -> os.aliases.contains(alias)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported OS: '%s'. Supported values are '%s'", alias, join(", ", supportedValues())))); + } + + private static List supportedValues() { + return Arrays.stream(values()) + .flatMap(os -> os.aliases.stream()) + .toList(); + } + } + + enum Arch { + X64("x86_64", "x86-64", "amd64", "x64"), + AARCH64("arm64", "aarch64"); + + private final List aliases; + + Arch(String... aliases) { + this.aliases = Arrays.stream(aliases).toList(); + } + + private static Arch from(String alias) { + return Arrays.stream(values()) + .filter(arch -> arch.aliases.contains(alias)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported architecture: '%s'. Supported values are '%s'", alias, join(", ", supportedValues())))); + } + + private static List supportedValues() { + return Arrays.stream(values()) + .flatMap(arch -> arch.aliases.stream()) + .toList(); + } + } +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/package-info.java new file mode 100644 index 00000000000..2a8ba4631e8 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.v2.api.analysis.service; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java index 8dd3091614a..186a5dd4c6c 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java @@ -43,8 +43,12 @@ import org.sonar.server.platform.NodeInformation; import org.sonar.server.rule.RuleDescriptionFormatter; import org.sonar.server.user.SystemPasscode; import org.sonar.server.user.UserSession; +import org.sonar.server.v2.api.analysis.controller.DefaultJresController; import org.sonar.server.v2.api.analysis.controller.DefaultVersionController; +import org.sonar.server.v2.api.analysis.controller.JresController; import org.sonar.server.v2.api.analysis.controller.VersionController; +import org.sonar.server.v2.api.analysis.service.JresHandler; +import org.sonar.server.v2.api.analysis.service.JresHandlerImpl; import org.sonar.server.v2.api.dop.controller.DefaultDopSettingsController; import org.sonar.server.v2.api.dop.controller.DopSettingsController; import org.sonar.server.v2.api.gitlab.config.controller.DefaultGitlabConfigurationController; @@ -159,4 +163,14 @@ public class PlatformLevel4WebConfig { return new DefaultVersionController(server); } + @Bean + public JresHandler jresHandler() { + return new JresHandlerImpl(); + } + + @Bean + public JresController jresController(JresHandler jresHandler) { + return new DefaultJresController(jresHandler); + } + } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultJresControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultJresControllerTest.java new file mode 100644 index 00000000000..c5f3a1cd343 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultJresControllerTest.java @@ -0,0 +1,132 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.controller; + +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; +import org.sonar.server.v2.api.analysis.service.JresHandler; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.v2.WebApiEndpoints.JRE_ENDPOINT; +import static org.sonar.server.v2.api.ControllerTester.getMockMvc; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class DefaultJresControllerTest { + + private final JresHandler jresHandler = mock(JresHandler.class); + + private final MockMvc mockMvc = getMockMvc(new DefaultJresController(jresHandler)); + + @ParameterizedTest + @MethodSource("osAndArch") + void getJres_shoudlReturnMetadaAsJson(String os, String arch) throws Exception { + JreInfoRestResponse metadata1 = new JreInfoRestResponse("id_1", "filename_1", "sha256_1", "javaPath_1", "os_1", "arch_1"); + JreInfoRestResponse metadata2 = new JreInfoRestResponse("id_2", "filename_2", "sha256_2", "javaPath_2", "os_2", "arch_2"); + when(jresHandler.getJresMetadata(os, arch)).thenReturn(List.of(metadata1, metadata2)); + String expectedJson = + """ + [ + { + "id": "id_1", + "filename": "filename_1", + "sha256": "sha256_1", + "javaPath": "javaPath_1", + "os": "os_1", + "arch": "arch_1" + }, + { + "id": "id_2", + "filename": "filename_2", + "sha256": "sha256_2", + "javaPath": "javaPath_2", + "os": "os_2", + "arch": "arch_2" + } + ] + """; + + mockMvc.perform(get(JRE_ENDPOINT) + .param("os", os) + .param("arch", arch)) + .andExpect(status().isOk()) + .andExpect(content().json(expectedJson)); + } + + @Test + void getJres_shoudlReturnEmptyJsonArray_whenNoResults() throws Exception { + when(jresHandler.getJresMetadata(null, null)).thenReturn(List.of()); + + mockMvc.perform(get(JRE_ENDPOINT)) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + } + + private static Stream osAndArch() { + return Stream.of( + arguments(null, null), + arguments("windows", null), + arguments(null, "x64"), + arguments("linux", "aarch64") + ); + } + + @Test + void getJre_shouldReturnMetadataAsJson() throws Exception { + String anyId = "anyId"; + JreInfoRestResponse jreInfoRestResponse = new JreInfoRestResponse(anyId, "filename", "sha256", "javaPath", "os", "arch"); + when(jresHandler.getJreMetadata(anyId)).thenReturn(jreInfoRestResponse); + String expectedJson = "{\"id\":\"" + anyId + "\",\"filename\":\"filename\",\"sha256\":\"sha256\",\"javaPath\":\"javaPath\",\"os\":\"os\",\"arch\":\"arch\"}"; + + mockMvc.perform(get(JRE_ENDPOINT + "/" + anyId)) + .andExpect(status().isOk()) + .andExpect(content().json(expectedJson)); + } + + @Test + void getJre_shouldDownloadJre_whenHeaderIsOctetStream() throws Exception { + String anyId = "anyId"; + String anyFilename = "anyFilename"; + JreInfoRestResponse jreInfoRestResponse = new JreInfoRestResponse(anyId, anyFilename, "sha256", "javaPath", "os", "arch"); + when(jresHandler.getJreMetadata(anyId)).thenReturn(jreInfoRestResponse); + byte[] anyBinary = {1, 2, 3}; + + when(jresHandler.getJreBinary(anyFilename)).thenReturn(new ByteArrayInputStream(anyBinary)); + + mockMvc.perform(get(JRE_ENDPOINT + "/" + anyId) + .header("Accept", APPLICATION_OCTET_STREAM_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_OCTET_STREAM)) + .andExpect(content().bytes(anyBinary)); + } + +} diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultVersionControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultVersionControllerTest.java index 5dfdf29a2a3..b907cb0f572 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultVersionControllerTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultVersionControllerTest.java @@ -19,7 +19,7 @@ */ package org.sonar.server.v2.api.analysis.controller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.platform.Server; import org.springframework.test.web.servlet.MockMvc; @@ -31,14 +31,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class DefaultVersionControllerTest { +class DefaultVersionControllerTest { private final Server server = mock(Server.class); private final MockMvc mockMvc = getMockMvc(new DefaultVersionController(server)); @Test - public void getVersion_shouldReturnServerVersion() throws Exception { + void getVersion_shouldReturnServerVersion() throws Exception { String serverVersion = "10.6"; when(server.getVersion()).thenReturn(serverVersion); diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/JresHandlerImplTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/JresHandlerImplTest.java new file mode 100644 index 00000000000..210abf41c8a --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/JresHandlerImplTest.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.v2.api.analysis.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.v2.api.analysis.response.JreInfoRestResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class JresHandlerImplTest { + + private static final Map JRE_METADATA = Map.of( + "1", new JreInfoRestResponse("1", "jre1", "checksum1", "java1", "alpine", "aarch64"), + "2", new JreInfoRestResponse("2", "jre2", "checksum2", "java2", "linux", "aarch64"), + "3", new JreInfoRestResponse("3", "jre3", "checksum3", "java3", "alpine", "x64") + ); + + private static final JresHandlerImpl jresHandler = new JresHandlerImpl("jres-metadata-tests.json"); + + @BeforeAll + static void setup() { + jresHandler.initMetadata(); + } + + @Test + void getJresMetadata_shouldReturnAllMetadata_whenNoFiltering() { + List result = jresHandler.getJresMetadata(null, null); + + assertThat(result).extracting(JreInfoRestResponse::id).containsExactly("1", "2", "3"); + } + + @ParameterizedTest + @MethodSource("filteredMetadata") + void getJresMetadata_shouldReturnFilteredMetadata_whenFiltering(String os, String arch, JreInfoRestResponse expected) { + List resultList = jresHandler.getJresMetadata(os, arch); + + assertThat(resultList).hasSize(1); + JreInfoRestResponse result = resultList.get(0); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void getJresMetadata_shouldFail_whenFilteredWithUnsupportedOsValue() { + String anyUnsupportedOS = "not-supported"; + + assertThatThrownBy(() -> jresHandler.getJresMetadata(anyUnsupportedOS, null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Unsupported OS: '" + anyUnsupportedOS + "'"); + } + + @Test + void getJresMetadata_shouldFail_whenFilteredWithUnsupportedArchValue() { + String anyUnsupportedArch = "not-supported"; + + assertThatThrownBy(() -> jresHandler.getJresMetadata(null, anyUnsupportedArch)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Unsupported architecture: '" + anyUnsupportedArch + "'"); + } + + private static Stream filteredMetadata() { + return Stream.of( + arguments("alpine", "aarch64", JRE_METADATA.get("1")), + arguments("linux", null, JRE_METADATA.get("2")), + arguments(null, "x64", JRE_METADATA.get("3")) + ); + } + + @Test + void getJresMetadata_shouldReturnEmptyList_whenNoMetadata() { + List result = jresHandler.getJresMetadata("windows", "x64"); + assertThat(result).isEmpty(); + } + + @Test + void getJresMetadata_shouldReturnEmptyList_whenNoFilteringAndNoMetadata() { + JresHandlerImpl noMetadataHandler = new JresHandlerImpl(""); + List result = noMetadataHandler.getJresMetadata(null, null); + assertThat(result).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"1", "2", "3"}) + void getJreMetadata(String id) { + JreInfoRestResponse result = jresHandler.getJreMetadata(id); + + assertThat(result).usingRecursiveComparison().isEqualTo(JRE_METADATA.get(id)); + } + + @Test + void getJreMetadata_shouldFail_whenJreNotFound() { + assertThatThrownBy(() -> jresHandler.getJreMetadata("4")) + .isInstanceOf(NotFoundException.class) + .hasMessage("JRE not found for id: 4"); + } + + @Test + void getJreBinary_shouldFail_whenFileNotFound() { + assertThatThrownBy(() -> jresHandler.getJreBinary("jre1")) + .isInstanceOf(NotFoundException.class) + .hasMessage("Unable to find JRE 'jre1'"); + } +} diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java index 5a032d3591f..0723c2d49b1 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java @@ -19,20 +19,35 @@ */ package org.sonar.server.v2.config; -import org.junit.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.sonar.api.platform.Server; +import org.sonar.server.v2.api.analysis.controller.DefaultJresController; import org.sonar.server.v2.api.analysis.controller.DefaultVersionController; +import org.sonar.server.v2.api.analysis.service.JresHandler; +import org.sonar.server.v2.api.analysis.service.JresHandlerImpl; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; public class PlatformLevel4WebConfigTest { - private final PlatformLevel4WebConfig platformLevel4WebConfig = new PlatformLevel4WebConfig(); + private static final PlatformLevel4WebConfig platformLevel4WebConfig = new PlatformLevel4WebConfig(); - @Test - public void versionController() { - assertThat(platformLevel4WebConfig.versionController(mock(Server.class))).isNotNull() - .isInstanceOf(DefaultVersionController.class); + private static Stream components() { + return Stream.of( + arguments(platformLevel4WebConfig.versionController(mock(Server.class)), DefaultVersionController.class), + arguments(platformLevel4WebConfig.jresHandler(), JresHandlerImpl.class), + arguments(platformLevel4WebConfig.jresController(mock(JresHandler.class)), DefaultJresController.class) + ); + } + + @ParameterizedTest + @MethodSource("components") + void components_shouldBeInjectedInPlatformLevel4WebConfig(Object component, Class instanceClass) { + assertThat(component).isNotNull().isInstanceOf(instanceClass); } } diff --git a/server/sonar-webserver-webapi-v2/src/test/resources/jres-metadata-tests.json b/server/sonar-webserver-webapi-v2/src/test/resources/jres-metadata-tests.json new file mode 100644 index 00000000000..2e3138021f6 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/resources/jres-metadata-tests.json @@ -0,0 +1,22 @@ +[ { + "id" : "1", + "filename" : "jre1", + "sha256" : "checksum1", + "javaPath" : "java1", + "os" : "alpine", + "arch" : "aarch64" +}, { + "id" : "2", + "filename" : "jre2", + "sha256" : "checksum2", + "javaPath" : "java2", + "os" : "linux", + "arch" : "aarch64" +}, { + "id" : "3", + "filename" : "jre3", + "sha256" : "checksum3", + "javaPath" : "java3", + "os" : "alpine", + "arch" : "x64" +} ] -- 2.39.5