From 2174a4a39b4f09a86d69dd1dafcbf7be7bea0053 Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Wed, 15 Feb 2023 11:48:25 +0100 Subject: [PATCH] SONAR-18484 Documentation for SonarQube Web API V2 (OpenApi/Swagger) --- build.gradle | 8 +++- .../sonar-web/public/WEB-INF/app-content.xml | 17 ------- server/sonar-webserver-webapi-v2/build.gradle | 6 ++- .../org/sonar/server/v2/LogComponent.java | 48 ------------------- .../RestResponseEntityExceptionHandler.java | 23 ++++++++- .../server/v2/config/CommonWebConfig.java | 17 +++++++ .../src/main/resources/springdoc.properties | 1 + server/sonar-webserver-webapi/build.gradle | 6 --- .../sonar/server/platform/PlatformImpl.java | 6 +-- sonar-application/build.gradle | 4 +- 10 files changed, 55 insertions(+), 81 deletions(-) delete mode 100644 server/sonar-web/public/WEB-INF/app-content.xml delete mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/LogComponent.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/resources/springdoc.properties diff --git a/build.gradle b/build.gradle index c08ad9fc4df..83e41aa32f3 100644 --- a/build.gradle +++ b/build.gradle @@ -158,6 +158,7 @@ subprojects { ext { protobufVersion = '3.21.12' + springVersion = '5.3.23' } sonar { @@ -329,6 +330,7 @@ subprojects { dependency('org.mockito:mockito-core:5.0.0') { exclude 'org.hamcrest:hamcrest-core' } + dependency "org.springframework:spring-test:${springVersion}" dependency 'org.mybatis:mybatis:3.5.11' dependencySet(group: 'org.slf4j', version: '2.0.6') { entry 'jcl-over-slf4j' @@ -341,9 +343,13 @@ subprojects { dependency 'org.simpleframework:simple:5.1.6' dependency 'org.sonarsource.orchestrator:sonar-orchestrator:3.40.0.183' dependency 'org.sonarsource.update-center:sonar-update-center-common:1.29.0.1000' - dependency('org.springframework:spring-context:5.3.23') { + dependency("org.springframework:spring-context:${springVersion}") { exclude 'commons-logging:commons-logging' } + dependency ("org.springframework:spring-webmvc:${springVersion}") { + exclude 'commons-logging:commons-logging' + } + dependency 'org.springdoc:springdoc-openapi-ui:1.6.14' dependency 'org.subethamail:subethasmtp:3.1.7' dependency 'org.yaml:snakeyaml:1.33' diff --git a/server/sonar-web/public/WEB-INF/app-content.xml b/server/sonar-web/public/WEB-INF/app-content.xml deleted file mode 100644 index 6593bb29d75..00000000000 --- a/server/sonar-web/public/WEB-INF/app-content.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/server/sonar-webserver-webapi-v2/build.gradle b/server/sonar-webserver-webapi-v2/build.gradle index 0c41cadea53..6366dad07d7 100644 --- a/server/sonar-webserver-webapi-v2/build.gradle +++ b/server/sonar-webserver-webapi-v2/build.gradle @@ -6,14 +6,16 @@ sonarqube { dependencies { // please keep the list grouped by configuration and ordered by name - api 'org.springframework:spring-webmvc:5.3.23' + api 'org.springdoc:springdoc-openapi-ui' + api 'org.springframework:spring-webmvc' api project(':server:sonar-db-dao') // We are not suppose to have a v1 dependency. The ideal would be to have another common module between webapi and webapi-v2 but that needs a lot of refactoring. api project(':server:sonar-webserver-webapi') + testImplementation 'org.mockito:mockito-core' - testImplementation 'org.springframework:spring-test:5.3.23' + testImplementation 'org.springframework:spring-test' testImplementation testFixtures(project(':server:sonar-server-common')) diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/LogComponent.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/LogComponent.java deleted file mode 100644 index 0fdaf56214c..00000000000 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/LogComponent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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; - -import java.util.List; -import java.util.stream.Collectors; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -@Component -public class LogComponent implements ApplicationListener { - - private static final Logger LOGGER = Loggers.get(LogComponent.class); - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - ApplicationContext applicationContext = event.getApplicationContext(); - List isUseless = applicationContext.getBeansOfType(RequestMappingHandlerMapping.class).values().stream() - .map(AbstractHandlerMethodMapping::getHandlerMethods) - .map(d->{ - d.forEach((e, c)-> LOGGER.info("Registered endpoint: "+e.getName()+" "+e.getDirectPaths()+" "+e)); - return 1; - }) - .collect(Collectors.toList()); - } -} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/RestResponseEntityExceptionHandler.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/RestResponseEntityExceptionHandler.java index 170914c5ec2..d62cc5c6996 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/RestResponseEntityExceptionHandler.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/RestResponseEntityExceptionHandler.java @@ -20,18 +20,39 @@ package org.sonar.server.v2.common; import java.util.Optional; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ServerException; +import org.sonar.server.exceptions.UnauthorizedException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class RestResponseEntityExceptionHandler { + @ExceptionHandler(IllegalStateException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + protected ResponseEntity handleIllegalStateException(IllegalStateException illegalStateException) { + return new ResponseEntity<>(illegalStateException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + protected ResponseEntity handleForbiddenException(ForbiddenException forbiddenException) { + return handleServerException(forbiddenException); + } + + @ExceptionHandler(UnauthorizedException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + protected ResponseEntity handleUnauthorizedException(UnauthorizedException unauthorizedException) { + return handleServerException(unauthorizedException); + } + + @ExceptionHandler(ServerException.class) protected ResponseEntity handleServerException(ServerException serverException) { return new ResponseEntity<>(serverException.getMessage(), Optional.ofNullable(HttpStatus.resolve(serverException.httpCode())).orElse(HttpStatus.INTERNAL_SERVER_ERROR)); } - } diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java index 23a1fdc678a..34c77693a30 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java @@ -19,13 +19,19 @@ */ package org.sonar.server.v2.config; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; import org.sonar.server.v2.common.RestResponseEntityExceptionHandler; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc +@ComponentScan(basePackages = {"org.springdoc"}) +@PropertySource("classpath:springdoc.properties") public class CommonWebConfig { @Bean @@ -33,4 +39,15 @@ public class CommonWebConfig { return new RestResponseEntityExceptionHandler(); } + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info( + new Info() + .title("SonarQube Web API") + .version("0.0.1 alpha") + .description("Documentation of SonarQube Web API") + ); + } + } diff --git a/server/sonar-webserver-webapi-v2/src/main/resources/springdoc.properties b/server/sonar-webserver-webapi-v2/src/main/resources/springdoc.properties new file mode 100644 index 00000000000..1f51a43105e --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/resources/springdoc.properties @@ -0,0 +1 @@ +springdoc.api-docs.path=/api-docs diff --git a/server/sonar-webserver-webapi/build.gradle b/server/sonar-webserver-webapi/build.gradle index b891af08f70..2dcc84a9f29 100644 --- a/server/sonar-webserver-webapi/build.gradle +++ b/server/sonar-webserver-webapi/build.gradle @@ -13,12 +13,6 @@ dependencies { api 'io.prometheus:simpleclient_common' api 'io.prometheus:simpleclient_servlet' - api 'org.springframework:spring-webmvc:5.3.23' - api 'org.springframework:spring-web:5.3.23' - api 'org.springframework:spring-context:5.3.23' - api 'org.springframework:spring-core:5.3.23' - testImplementation 'org.springframework:spring-test:5.3.23' - api project(':server:sonar-ce-common') api project(':server:sonar-ce-task') api project(':server:sonar-db-dao') diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/PlatformImpl.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/PlatformImpl.java index a89de3719d8..5cfd89446b9 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/PlatformImpl.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/PlatformImpl.java @@ -31,7 +31,6 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; import org.sonar.core.platform.ExtensionContainer; import org.sonar.core.platform.SpringComponentContainer; -import org.sonar.server.platform.web.ApiV2Servlet; import org.sonar.server.app.ProcessCommandWrapper; import org.sonar.server.platform.db.migration.version.DatabaseVersion; import org.sonar.server.platform.platformlevel.PlatformLevel; @@ -41,7 +40,7 @@ import org.sonar.server.platform.platformlevel.PlatformLevel3; import org.sonar.server.platform.platformlevel.PlatformLevel4; import org.sonar.server.platform.platformlevel.PlatformLevelSafeMode; import org.sonar.server.platform.platformlevel.PlatformLevelStartup; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.sonar.server.platform.web.ApiV2Servlet; import static org.sonar.process.ProcessId.WEB_SERVER; @@ -63,12 +62,11 @@ public class PlatformImpl implements Platform { private PlatformLevel level3 = null; private PlatformLevel level4 = null; private PlatformLevel currentLevel = null; - private AnnotationConfigWebApplicationContext springMvcContext = null; private boolean dbConnected = false; private boolean started = false; private final List level4AddedComponents = new ArrayList<>(); private final Profiler profiler = Profiler.createIfTrace(Loggers.get(PlatformImpl.class)); - private ApiV2Servlet servlet; + private ApiV2Servlet servlet = null; public static PlatformImpl getInstance() { return INSTANCE; diff --git a/sonar-application/build.gradle b/sonar-application/build.gradle index 94defd62f61..6a9d0c34e75 100644 --- a/sonar-application/build.gradle +++ b/sonar-application/build.gradle @@ -43,7 +43,7 @@ jar.enabled = false shadowJar { archiveBaseName = 'sonar-application' archiveClassifier = null - mergeServiceFiles('META-INF/spring.*') + mergeServiceFiles() manifest { attributes('Main-Class': 'org.sonar.application.App') } @@ -309,7 +309,7 @@ task zip(type: Zip, dependsOn: [configurations.compileClasspath, downloadElastic // Check the size of the archive zip.doLast { def minLength = 320000000 - def maxLength = 345000000 + def maxLength = 355000000 def length = archiveFile.get().asFile.length() if (length < minLength) -- 2.39.5