]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22034 New Endpoints to get JREs metadata and download JRE
authorantoine.vinot <antoine.vinot@sonarsource.com>
Tue, 30 Apr 2024 10:05:46 +0000 (12:05 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 3 May 2024 20:02:50 +0000 (20:02 +0000)
15 files changed:
server/sonar-webserver-webapi-v2/build.gradle
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultJresController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/JresController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/JreInfoRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/package-info.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandler.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/JresHandlerImpl.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/package-info.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultJresControllerTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultVersionControllerTest.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/JresHandlerImplTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java
server/sonar-webserver-webapi-v2/src/test/resources/jres-metadata-tests.json [new file with mode: 0644]

index 760f4a2519853b45991593f06ddf364822e33166..2acb31872015007b3e1dcf1a236ec90fe811d9ac 100644 (file)
@@ -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'
index 336ae93ad2e6f0ada8034da38b7b0e9a9bb9f7ce..9ba51c1c17a68007efac1659cfeb370e882338b3 100644 (file)
@@ -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 (file)
index 0000000..da3e643
--- /dev/null
@@ -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<JreInfoRestResponse> 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 (file)
index 0000000..7e18f63
--- /dev/null
@@ -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<JreInfoRestResponse> 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 (file)
index 0000000..63f1853
--- /dev/null
@@ -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 (file)
index 0000000..3f469e8
--- /dev/null
@@ -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 (file)
index 0000000..3f4f56e
--- /dev/null
@@ -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<JreInfoRestResponse> 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 (file)
index 0000000..53a06aa
--- /dev/null
@@ -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<String, JreInfoRestResponse> 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<JreInfoRestResponse> 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<JreInfoRestResponse> getJresMetadata(@Nullable String os, @Nullable String arch) {
+    Predicate<JreInfoRestResponse> osFilter = isBlank(os) ? jre -> true : (jre -> OS.from(jre.os()) == OS.from(os));
+    Predicate<JreInfoRestResponse> 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<String> 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<String> 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<String> 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<String> 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 (file)
index 0000000..2a8ba46
--- /dev/null
@@ -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;
index 8dd3091614a30da08ac92446dd0461475b222cf6..186a5dd4c6c5f34ecb53055c0d4a99eb01be9bdd 100644 (file)
@@ -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 (file)
index 0000000..c5f3a1c
--- /dev/null
@@ -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<Arguments> 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));
+  }
+
+}
index 5dfdf29a2a3a7eacb3a52699a64166d1d35056a2..b907cb0f572a7af98e8a5d5253a9af1233551d0e 100644 (file)
@@ -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 (file)
index 0000000..210abf4
--- /dev/null
@@ -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<String, JreInfoRestResponse> 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<JreInfoRestResponse> 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<JreInfoRestResponse> 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<Arguments> 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<JreInfoRestResponse> result = jresHandler.getJresMetadata("windows", "x64");
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  void getJresMetadata_shouldReturnEmptyList_whenNoFilteringAndNoMetadata() {
+    JresHandlerImpl noMetadataHandler = new JresHandlerImpl("");
+    List<JreInfoRestResponse> 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'");
+  }
+}
index 5a032d3591f31c2b499c2af67c7ed5255c6769e5..0723c2d49b14b7b55439179bf5cf0711f91fd91e 100644 (file)
  */
 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<Arguments> 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 (file)
index 0000000..2e31380
--- /dev/null
@@ -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"
+} ]