From 8cf7b90f7368ab1be0d73cf2bb225ca326438f51 Mon Sep 17 00:00:00 2001 From: David An Date: Wed, 3 Jan 2024 12:53:15 -0500 Subject: [PATCH] AJ-1157: Spring Boot 3.2.1 (#440) --- build.gradle | 8 +- client/build.gradle | 6 +- client/swagger.gradle | 6 +- service/build.gradle | 53 ++++---- .../InstanceInitializerBean.java | 2 +- .../controller/GlobalExceptionHandler.java | 112 +++++++++++++++++ .../datarepo/HttpDataRepoClientFactory.java | 2 +- .../generated/CapabilitiesApi.java | 6 +- .../generated/CapabilitiesServerModel.java | 6 +- .../generated/GenericJobAllOfServerModel.java | 6 +- .../generated/GenericJobServerModel.java | 6 +- .../generated/ImportApi.java | 6 +- .../generated/ImportRequestServerModel.java | 6 +- .../generated/JobApi.java | 6 +- .../generated/JobV1ServerModel.java | 6 +- .../leonardo/LeonardoServiceException.java | 2 +- .../retry/RetryLoggingListener.java | 4 +- .../sam/BearerTokenFilter.java | 12 +- .../sam/TokenContextUtil.java | 2 +- .../service/MDCResponseHeaderFilter.java | 12 +- .../service/MDCServletRequestListener.java | 6 +- .../WorkspaceDataServiceException.java | 2 +- .../HttpWorkspaceManagerClientFactory.java | 2 +- .../WorkspaceManagerException.java | 2 +- service/src/main/resources/application.yml | 8 ++ service/src/main/resources/logback-spring.xml | 113 +++++++++--------- .../GlobalExceptionHandlerTest.java | 75 ++++++++++++ .../datarepo/DataRepoDaoTest.java | 2 +- .../leonardo/LeonardoDaoTest.java | 2 +- .../metrics/MetricsConfigTest.java | 2 +- .../pact/SamPactTest.java | 5 +- .../pact/WsmPactTest.java | 4 +- .../retry/RestClientRetryTest.java | 4 +- .../InstanceServiceSamExceptionTest.java | 4 +- .../RecordOrchestratorServiceTest.java | 2 +- .../WorkspaceManagerDaoTest.java | 2 +- service/src/test/resources/logback-spring.xml | 34 +++--- 37 files changed, 355 insertions(+), 183 deletions(-) create mode 100644 service/src/main/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandler.java create mode 100644 service/src/test/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandlerTest.java diff --git a/build.gradle b/build.gradle index f3e3adbe0..83e45ecad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { - id 'org.springframework.boot' version '2.7.18' apply false + id 'org.springframework.boot' version '3.2.1' apply false id 'io.spring.dependency-management' version '1.1.4' apply false id 'com.google.cloud.tools.jib' version '3.2.1' apply false id "org.sonarqube" version "4.4.1.3373" apply false @@ -36,12 +36,8 @@ subprojects { imports { mavenBom(SpringBootPlugin.BOM_COORDINATES) } - dependencies { - dependency 'org.yaml:snakeyaml:2.0' - dependency 'org.webjars:webjars-locator-core:0.53' - dependency 'ch.qos.logback:logback-classic:1.2.13+' // CVE-2023-6378 - dependency 'ch.qos.logback:logback-core:1.2.13+' // CVE-2023-6378 + dependency 'org.liquibase:liquibase-core:4.25.1' } } } diff --git a/client/build.gradle b/client/build.gradle index c13c36f82..d95cae570 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -21,10 +21,10 @@ ext { // default to javax, since WDS is currently on Spring Boot 2. -Pjakarta=true overrides // the default and creates a Jakarta-based client. -if (project.hasProperty("jakarta") && "true".equalsIgnoreCase(project.getProperty("jakarta"))) { - apply from: "dependencies-jakarta.gradle" -} else { +if (project.hasProperty("jakarta") && "false".equalsIgnoreCase(project.getProperty("jakarta"))) { apply from: "dependencies-javax.gradle" +} else { + apply from: "dependencies-jakarta.gradle" } apply from: 'artifactory.gradle' diff --git a/client/swagger.gradle b/client/swagger.gradle index e4a666a38..05a28cdac 100644 --- a/client/swagger.gradle +++ b/client/swagger.gradle @@ -1,9 +1,9 @@ dependencies { //Need to include libraries for generated code to work - api 'com.google.code.gson:gson:2.10.1' + api 'com.google.code.gson:gson' api 'io.gsonfire:gson-fire:1.9.0' - api 'com.squareup.okhttp3:okhttp:4.12.0' - api 'com.squareup.okhttp3:logging-interceptor:4.12.0' + api 'com.squareup.okhttp3:okhttp' + api 'com.squareup.okhttp3:logging-interceptor' } openApiValidate { diff --git a/service/build.gradle b/service/build.gradle index 2e37348e4..99862105d 100644 --- a/service/build.gradle +++ b/service/build.gradle @@ -36,7 +36,6 @@ jib { } ext { - jersey_version = "2.36" parquet_mr_version = "1.13.1" hadoop_version = "3.3.6" } @@ -57,29 +56,29 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' implementation 'org.springframework.integration:spring-integration-jdbc' - implementation 'org.aspectj:aspectjweaver:1.8.9' // required by spring-retry, not used directly + implementation 'org.aspectj:aspectjweaver' // required by spring-retry, not used directly implementation 'com.google.guava:guava:32.1.3-jre' implementation 'org.postgresql:postgresql' implementation 'org.webjars:webjars-locator-core' // versioned by spring dependency management - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.13.5' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv' implementation 'io.sentry:sentry-logback:6.34.0' - implementation 'org.liquibase:liquibase-core:4.21.1' + implementation 'org.liquibase:liquibase-core' implementation 'javax.cache:cache-api' - implementation 'org.ehcache:ehcache:3.10.8' + implementation 'org.ehcache:ehcache:3.10.8:jakarta' implementation 'org.hashids:hashids:1.0.3' - implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1' + implementation 'jakarta.ws.rs:jakarta.ws.rs-api' implementation 'com.google.mug:mug:6.6' // required by openapi-generated models and api interfaces - implementation 'javax.validation:validation-api' + implementation 'jakarta.validation:jakarta.validation-api' implementation 'io.swagger.core.v3:swagger-annotations:2.2.16' // Terra libraries - implementation group: 'org.broadinstitute.dsde.workbench', name: 'sam-client_2.13', version: '0.1-08b6588' + implementation group: 'org.broadinstitute.dsde.workbench', name: 'sam-client_2.13', version: '0.1-752b4f3' implementation group: 'org.broadinstitute.dsde.workbench', name: 'leonardo-client_2.13', version: '1.3.6-22ee00b' - implementation 'com.squareup.okhttp3:okhttp:4.12.0' // required by Sam client - implementation "bio.terra:datarepo-client:1.537.0-SNAPSHOT" - implementation "bio.terra:workspace-manager-client:0.254.717-SNAPSHOT" + implementation 'com.squareup.okhttp3:okhttp' // required by Sam client + implementation "bio.terra:datarepo-jakarta-client:1.557.0-SNAPSHOT" + implementation "bio.terra:workspace-manager-client:0.254.983-SNAPSHOT" implementation "bio.terra:java-pfb-library:0.15.0" implementation project(path: ':client') @@ -106,15 +105,15 @@ dependencies { implementation "org.apache.hadoop:hadoop-common:$hadoop_version", withoutHadoopExcludes implementation "org.apache.hadoop:hadoop-mapreduce-client-core:$hadoop_version", withoutHadoopExcludes - // hk2 is required to use WSM client, but not correctly exposed by the client - implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version" - implementation "org.glassfish.jersey.core:jersey-client:$jersey_version" - implementation "org.glassfish.jersey.core:jersey-common:$jersey_version" + // Jersey libs, required by WSM client (and maybe other clients?), version managed by Spring + implementation "org.glassfish.jersey.inject:jersey-hk2" + implementation "org.glassfish.jersey.core:jersey-client" + implementation "org.glassfish.jersey.core:jersey-common" annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' runtimeOnly 'org.webjars.npm:swagger-ui-dist:5.9.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation project(':client') testImplementation 'au.com.dius.pact.consumer:junit5:4.6.1' testImplementation 'org.junit-pioneer:junit-pioneer:2.1.0' @@ -127,18 +126,6 @@ dependencies { implementation('org.apache.commons:commons-compress:1.24.0') { because("CVE-2023-42503") } - - - // this is redundant (and ineffective) without an explicit override in build.gradle - // dependencyManagement, but left here for documentation purposes - implementation('ch.qos.logback:logback-classic:1.2.13+') { - because("CVE-2023-6378") - } - // this is redundant (and ineffective) without an explicit override in build.gradle - // dependencyManagement, but left here for documentation purposes - implementation('ch.qos.logback:logback-core:1.2.13+') { - because("CVE-2023-6378") - } } } @@ -159,10 +146,12 @@ tasks.register('generateApiInterfaces', GenerateTask) { supportingFilesConstrainedTo.set([]) // empty array means generate none skipOperationExample = true // example responses require the ApiUtil.java supporting file configOptions.set([ + useJakartaEe : "true", // for Spring Boot 3 interfaceOnly : "true", // Java interfaces only; no controllers or implementation classes useTags : "true", // use the OpenAPI tag to classify apis, not the first segment of the api path hideGenerationTimestamp: "true" // hidden to prevent unnecessary diff noise on unrelated changes ]) + } @@ -252,10 +241,10 @@ testing { implementation project(':client') implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-jdbc' - implementation 'org.liquibase:liquibase-core:4.21.1' - implementation "org.glassfish.jersey.core:jersey-client:$jersey_version" - implementation "org.glassfish.jersey.core:jersey-common:$jersey_version" - implementation "org.glassfish.jersey.inject:jersey-hk2:$jersey_version" + implementation 'org.liquibase:liquibase-core' + implementation "org.glassfish.jersey.core:jersey-client" + implementation "org.glassfish.jersey.core:jersey-common" + implementation "org.glassfish.jersey.inject:jersey-hk2" } } } diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/InstanceInitializerBean.java b/service/src/main/java/org/databiosphere/workspacedataservice/InstanceInitializerBean.java index dc602b332..8ce501091 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/InstanceInitializerBean.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/InstanceInitializerBean.java @@ -247,7 +247,7 @@ private String requestRemoteBackup(UUID trackingId) { } catch (WorkspaceDataServiceException wdsE) { if (wdsE.getCause() != null && wdsE.getCause() instanceof RestException restException - && restException.getStatus() == HttpStatus.NOT_FOUND) { + && restException.getStatusCode() == HttpStatus.NOT_FOUND) { LOGGER.error( "Remote source WDS in workspace {} does not support cloning", sourceWorkspaceId); cloneDao.terminateCloneToError( diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandler.java b/service/src/main/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandler.java new file mode 100644 index 000000000..30e643b1f --- /dev/null +++ b/service/src/main/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandler.java @@ -0,0 +1,112 @@ +package org.databiosphere.workspacedataservice.controller; + +import com.google.common.annotations.VisibleForTesting; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + private static final String ERROR = "error"; + private static final String MESSAGE = "message"; + private static final String PATH = "path"; + private static final String STATUS = "status"; + private static final String TIMESTAMP = "timestamp"; + + /** + * Override to explicitly translate Problem Details structures back to {@link + * org.databiosphere.workspacedata.model.ErrorResponse} structures. This ensures backwards + * compatibility for clients. + * + *

Even though {@code spring.mvc.problemdetails.enabled} is set to false in config, many + * exception classes extend {@link org.springframework.web.server.ResponseStatusException}, and + * that exception serializes to a problem detail in the response. + * + * @param body the body to use for the response + * @param headers the headers to use for the response + * @param statusCode the status code to use for the response + * @param request the current request + * @return the {@code ResponseEntity} instance to use + */ + @NotNull + @Override + protected ResponseEntity createResponseEntity( + Object body, + @NotNull HttpHeaders headers, + @NotNull HttpStatusCode statusCode, + @NotNull WebRequest request) { + if (body instanceof ProblemDetail problemDetail) { + Map errorBody = new LinkedHashMap<>(); + errorBody.put(ERROR, problemDetail.getTitle()); + errorBody.put(MESSAGE, problemDetail.getDetail()); + errorBody.put(STATUS, problemDetail.getStatus()); + errorBody.put(TIMESTAMP, new Date()); + + if (request instanceof ServletWebRequest servletWebRequest) { + errorBody.put(PATH, servletWebRequest.getRequest().getRequestURI()); + } + return new ResponseEntity<>(errorBody, headers, statusCode); + } + + return super.createResponseEntity(body, headers, statusCode, request); + } + + /** + * Handler for MethodArgumentTypeMismatchException. This exception contains the real message we + * want to display inside the nested cause. This handler ditches the top-level message in favor of + * that nested one. + * + * @param ex the exception in question + * @param servletRequest the request + * @return the desired response + */ + @ExceptionHandler({MethodArgumentTypeMismatchException.class}) + public ResponseEntity> handleAllExceptions( + Exception ex, HttpServletRequest servletRequest) { + Map errorBody = new LinkedHashMap<>(); + errorBody.put(ERROR, HttpStatus.BAD_REQUEST.getReasonPhrase()); + errorBody.put(PATH, servletRequest.getRequestURI()); + errorBody.put(STATUS, HttpStatus.BAD_REQUEST.value()); + errorBody.put(TIMESTAMP, new Date()); + + // MethodArgumentTypeMismatchException nested exceptions contains + // the real message we want to display. Gather all the nested messages and display the last one. + List errorMessages = gatherNestedErrorMessages(ex, new ArrayList<>()); + if (!errorMessages.isEmpty()) { + errorBody.put(MESSAGE, errorMessages.get(errorMessages.size() - 1)); + } else { + errorBody.put(MESSAGE, "Unexpected error: " + ex.getClass().getName()); + } + + return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON).body(errorBody); + } + + @VisibleForTesting + List gatherNestedErrorMessages(@NotNull Throwable t, @NotNull List accumulator) { + if (StringUtils.isNotBlank(t.getMessage())) { + accumulator.add(t.getMessage()); + } + if (t.getCause() == null) { + return accumulator; + } + return gatherNestedErrorMessages(t.getCause(), accumulator); + } +} diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/datarepo/HttpDataRepoClientFactory.java b/service/src/main/java/org/databiosphere/workspacedataservice/datarepo/HttpDataRepoClientFactory.java index cd788fd44..fea385cd7 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/datarepo/HttpDataRepoClientFactory.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/datarepo/HttpDataRepoClientFactory.java @@ -2,7 +2,7 @@ import bio.terra.datarepo.api.RepositoryApi; import bio.terra.datarepo.client.ApiClient; -import javax.ws.rs.client.Client; +import jakarta.ws.rs.client.Client; import org.apache.commons.lang3.StringUtils; import org.databiosphere.workspacedataservice.sam.TokenContextUtil; import org.databiosphere.workspacedataservice.shared.model.BearerToken; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesApi.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesApi.java index e043b371d..355a0fb62 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesApi.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesApi.java @@ -25,12 +25,12 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import java.util.List; import java.util.Map; import java.util.Optional; -import javax.annotation.Generated; +import jakarta.annotation.Generated; @Generated(value = "org.openapitools.codegen.languages.SpringCodegen") @Validated diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesServerModel.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesServerModel.java index e538d76d9..0e9541818 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesServerModel.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/CapabilitiesServerModel.java @@ -9,13 +9,13 @@ import java.util.Map; import org.openapitools.jackson.nullable.JsonNullable; import java.time.OffsetDateTime; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; -import javax.annotation.Generated; +import jakarta.annotation.Generated; /** * CapabilitiesServerModel diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobAllOfServerModel.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobAllOfServerModel.java index 15ae9b345..91d45c8cf 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobAllOfServerModel.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobAllOfServerModel.java @@ -7,13 +7,13 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import org.openapitools.jackson.nullable.JsonNullable; import java.time.OffsetDateTime; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; -import javax.annotation.Generated; +import jakarta.annotation.Generated; /** * GenericJobAllOfServerModel diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobServerModel.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobServerModel.java index 3ba356ab9..c9920fed1 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobServerModel.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/GenericJobServerModel.java @@ -11,13 +11,13 @@ import org.springframework.format.annotation.DateTimeFormat; import org.openapitools.jackson.nullable.JsonNullable; import java.time.OffsetDateTime; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; -import javax.annotation.Generated; +import jakarta.annotation.Generated; /** * Generic representation of a job, no opinion on inputs and result for the job. See individual APIs for more guidance on expected input and result payloads. diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportApi.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportApi.java index 18c57b95d..edf1831cd 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportApi.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportApi.java @@ -27,12 +27,12 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import java.util.List; import java.util.Map; import java.util.Optional; -import javax.annotation.Generated; +import jakarta.annotation.Generated; @Generated(value = "org.openapitools.codegen.languages.SpringCodegen") @Validated diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportRequestServerModel.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportRequestServerModel.java index 29d91c108..bc96efc44 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportRequestServerModel.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/ImportRequestServerModel.java @@ -11,13 +11,13 @@ import java.util.Map; import org.openapitools.jackson.nullable.JsonNullable; import java.time.OffsetDateTime; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; -import javax.annotation.Generated; +import jakarta.annotation.Generated; /** * ImportRequestServerModel diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobApi.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobApi.java index c5be8385d..316e91ffd 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobApi.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobApi.java @@ -26,12 +26,12 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import java.util.List; import java.util.Map; import java.util.Optional; -import javax.annotation.Generated; +import jakarta.annotation.Generated; @Generated(value = "org.openapitools.codegen.languages.SpringCodegen") @Validated diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobV1ServerModel.java b/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobV1ServerModel.java index 7b9dc4158..e4fe75a53 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobV1ServerModel.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/generated/JobV1ServerModel.java @@ -11,13 +11,13 @@ import org.springframework.format.annotation.DateTimeFormat; import org.openapitools.jackson.nullable.JsonNullable; import java.time.OffsetDateTime; -import javax.validation.Valid; -import javax.validation.constraints.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; import io.swagger.v3.oas.annotations.media.Schema; import java.util.*; -import javax.annotation.Generated; +import jakarta.annotation.Generated; /** * JobV1ServerModel diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/leonardo/LeonardoServiceException.java b/service/src/main/java/org/databiosphere/workspacedataservice/leonardo/LeonardoServiceException.java index d4382716a..d566fb0e0 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/leonardo/LeonardoServiceException.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/leonardo/LeonardoServiceException.java @@ -5,6 +5,6 @@ public class LeonardoServiceException extends ResponseStatusException { public LeonardoServiceException(RestException cause) { - super(cause.getStatus(), cause.getMessage(), cause); + super(cause.getStatusCode(), cause.getMessage(), cause); } } diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/retry/RetryLoggingListener.java b/service/src/main/java/org/databiosphere/workspacedataservice/retry/RetryLoggingListener.java index b91e3f0bf..a90c15149 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/retry/RetryLoggingListener.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/retry/RetryLoggingListener.java @@ -4,11 +4,11 @@ import org.slf4j.LoggerFactory; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; -import org.springframework.retry.listener.RetryListenerSupport; +import org.springframework.retry.RetryListener; import org.springframework.stereotype.Component; @Component("retryLoggingListener") -public class RetryLoggingListener extends RetryListenerSupport { +public class RetryLoggingListener implements RetryListener { private final Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/sam/BearerTokenFilter.java b/service/src/main/java/org/databiosphere/workspacedataservice/sam/BearerTokenFilter.java index 0b80e0647..4648fc701 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/sam/BearerTokenFilter.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/sam/BearerTokenFilter.java @@ -2,14 +2,14 @@ import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Objects; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/sam/TokenContextUtil.java b/service/src/main/java/org/databiosphere/workspacedataservice/sam/TokenContextUtil.java index c5eb7eab8..47b13aa78 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/sam/TokenContextUtil.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/sam/TokenContextUtil.java @@ -3,9 +3,9 @@ import static org.databiosphere.workspacedataservice.sam.BearerTokenFilter.ATTRIBUTE_NAME_TOKEN; import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST; +import jakarta.validation.constraints.NotNull; import java.util.Map; import java.util.function.Supplier; -import javax.validation.constraints.NotNull; import org.databiosphere.workspacedataservice.jobexec.JobContextHolder; import org.databiosphere.workspacedataservice.shared.model.BearerToken; import org.slf4j.Logger; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCResponseHeaderFilter.java b/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCResponseHeaderFilter.java index 5e9a64238..d849a01b0 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCResponseHeaderFilter.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCResponseHeaderFilter.java @@ -1,12 +1,12 @@ package org.databiosphere.workspacedataservice.service; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; import org.slf4j.MDC; import org.springframework.stereotype.Component; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCServletRequestListener.java b/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCServletRequestListener.java index 97d8ce82e..8b1f0e8dc 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCServletRequestListener.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/service/MDCServletRequestListener.java @@ -1,11 +1,11 @@ package org.databiosphere.workspacedataservice.service; +import jakarta.servlet.ServletRequestEvent; +import jakarta.servlet.ServletRequestListener; +import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.http.HttpServletRequest; import org.hashids.Hashids; import org.slf4j.MDC; import org.springframework.stereotype.Component; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/sourcewds/WorkspaceDataServiceException.java b/service/src/main/java/org/databiosphere/workspacedataservice/sourcewds/WorkspaceDataServiceException.java index 80fbd0497..e5ff29e47 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/sourcewds/WorkspaceDataServiceException.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/sourcewds/WorkspaceDataServiceException.java @@ -16,6 +16,6 @@ public WorkspaceDataServiceException(ApiException cause) { } public WorkspaceDataServiceException(RestException cause) { - super(cause.getStatus(), cause.getMessage(), cause); + super(cause.getStatusCode(), cause.getMessage(), cause); } } diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/HttpWorkspaceManagerClientFactory.java b/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/HttpWorkspaceManagerClientFactory.java index 70962fa25..b87d1a420 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/HttpWorkspaceManagerClientFactory.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/HttpWorkspaceManagerClientFactory.java @@ -4,7 +4,7 @@ import bio.terra.workspace.api.ReferencedGcpResourceApi; import bio.terra.workspace.api.ResourceApi; import bio.terra.workspace.client.ApiClient; -import javax.ws.rs.client.Client; +import jakarta.ws.rs.client.Client; import org.apache.commons.lang3.StringUtils; import org.databiosphere.workspacedataservice.sam.TokenContextUtil; import org.databiosphere.workspacedataservice.shared.model.BearerToken; diff --git a/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerException.java b/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerException.java index 563516124..29d7379b7 100644 --- a/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerException.java +++ b/service/src/main/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerException.java @@ -5,6 +5,6 @@ public class WorkspaceManagerException extends ResponseStatusException { public WorkspaceManagerException(RestException cause) { - super(cause.getStatus(), cause.getMessage(), cause); + super(cause.getStatusCode(), cause.getMessage(), cause); } } diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 8528f3349..0cc779346 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -39,6 +39,10 @@ management: info: env: enabled: true + prometheus: + metrics: + export: + enabled: true info: app: @@ -57,6 +61,10 @@ spring: password: ${env.wds.db.password} maximum-pool-size: 7 minimum-idle: 7 + mvc: + problemdetails: + # TODO AJ-1157: can we turn this back on? + enabled: false servlet: multipart.max-request-size: 5GB multipart.max-file-size: 5GB diff --git a/service/src/main/resources/logback-spring.xml b/service/src/main/resources/logback-spring.xml index 30c346292..bad3b9f1c 100644 --- a/service/src/main/resources/logback-spring.xml +++ b/service/src/main/resources/logback-spring.xml @@ -1,63 +1,60 @@ - - - - - - - - ${WDS_LOG_PATTERN} - ${CONSOLE_LOG_CHARSET} - - - - - - ERROR - - INFO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + ERROR + + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandlerTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandlerTest.java new file mode 100644 index 000000000..0ab00249d --- /dev/null +++ b/service/src/test/java/org/databiosphere/workspacedataservice/controller/GlobalExceptionHandlerTest.java @@ -0,0 +1,75 @@ +package org.databiosphere.workspacedataservice.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class GlobalExceptionHandlerTest { + @Autowired private GlobalExceptionHandler globalExceptionHandler; + + @Test + void gatherNestedErrorMessagesNullMessage() { + Throwable input = new Throwable(); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).isEmpty(); + } + + @Test + void gatherNestedErrorMessagesEmptyMessage() { + Throwable input = new Throwable(""); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).isEmpty(); + } + + @Test + void gatherNestedErrorMessagesBlankMessage() { + Throwable input = new Throwable(" "); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).isEmpty(); + } + + @Test + void gatherNestedErrorMessagesOneMessage() { + Throwable input = new Throwable("one"); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).size().isEqualTo(1); + assertEquals("one", actual.get(0)); + } + + @Test + void gatherNestedErrorMessagesTwoMessages() { + Throwable input = new Throwable("one"); + Throwable nested = new Throwable("two"); + input.initCause(nested); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).size().isEqualTo(2); + assertEquals("one", actual.get(0)); + assertEquals("two", actual.get(1)); + } + + @Test + void gatherNestedErrorMessagesThreeMessages() { + Throwable input = new Throwable("one"); + Throwable nested = new Throwable("two"); + Throwable third = new Throwable("three"); + nested.initCause(third); + input.initCause(nested); + List actual = + globalExceptionHandler.gatherNestedErrorMessages(input, new ArrayList<>()); + assertThat(actual).size().isEqualTo(3); + assertEquals("one", actual.get(0)); + assertEquals("two", actual.get(1)); + assertEquals("three", actual.get(2)); + } +} diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/datarepo/DataRepoDaoTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/datarepo/DataRepoDaoTest.java index d6393217e..aef23f4cf 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/datarepo/DataRepoDaoTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/datarepo/DataRepoDaoTest.java @@ -68,7 +68,7 @@ void testErrorThrown() throws ApiException { .willThrow(new ApiException(statusCode, "Intentional error thrown for unit test")); var exception = assertThrows(DataRepoException.class, () -> dataRepoDao.getSnapshot(UUID.randomUUID())); - assertEquals(statusCode, exception.getRawStatusCode()); + assertEquals(statusCode, exception.getStatusCode().value()); Mockito.clearInvocations(mockRepositoryApi); } } diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/leonardo/LeonardoDaoTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/leonardo/LeonardoDaoTest.java index 104aeab21..ba9686b38 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/leonardo/LeonardoDaoTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/leonardo/LeonardoDaoTest.java @@ -57,7 +57,7 @@ void testWdsUrlNotReturned() throws ApiException { statusCode, "Intentional error thrown for unit test")); var exception = assertThrows(LeonardoServiceException.class, () -> leonardoDao.getWdsEndpointUrl(any())); - assertEquals(statusCode, exception.getRawStatusCode()); + assertEquals(statusCode, exception.getStatusCode().value()); } @Test diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/metrics/MetricsConfigTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/metrics/MetricsConfigTest.java index f0bbc0788..6f72a0866 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/metrics/MetricsConfigTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/metrics/MetricsConfigTest.java @@ -21,7 +21,7 @@ import org.springframework.test.web.servlet.MockMvc; @DirtiesContext -@SpringBootTest(properties = "management.metrics.export.prometheus.enabled=true") +@SpringBootTest(properties = "management.prometheus.metrics.export.enabled=true") @AutoConfigureMockMvc class MetricsConfigTest { @Autowired private BuildProperties buildProperties; diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/pact/SamPactTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/pact/SamPactTest.java index 37590055e..c1823fd6d 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/pact/SamPactTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/pact/SamPactTest.java @@ -48,7 +48,7 @@ public RequestResponsePact statusApiPact(PactDslWithProvider builder) { .method("GET") .willRespondWith() .status(200) - .body("{\"ok\": true}") + .body("{\"ok\": true, \"systems\": {}}") .toPact(); } @@ -61,7 +61,7 @@ public RequestResponsePact downStatusApiPact(PactDslWithProvider builder) { .method("GET") .willRespondWith() .status(500) - .body("{\"ok\": false}") + .body("{\"ok\": false, \"systems\": {}}") .toPact(); } @@ -131,6 +131,7 @@ public RequestResponsePact userStatusPact(PactDslWithProvider builder) { new PactDslJsonBody() .stringType("userSubjectId") .stringType("userEmail") + .booleanType("adminEnabled") .booleanType("enabled"); return builder .given("user status info request with access token") diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/pact/WsmPactTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/pact/WsmPactTest.java index 8e818303b..54c866226 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/pact/WsmPactTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/pact/WsmPactTest.java @@ -480,7 +480,7 @@ void getBlobStorageUrl_forbidden(MockServer mockServer) { assertThrows( WorkspaceManagerException.class, () -> wsmDao.getBlobStorageUrl(WORKSPACE_UUID.toString(), BEARER_TOKEN)); - assertEquals(HttpStatus.FORBIDDEN, thrown.getStatus()); + assertEquals(HttpStatus.FORBIDDEN, thrown.getStatusCode()); } @Test @@ -494,7 +494,7 @@ void getBlobStorageUrl_noStorageContainers(MockServer mockServer) { assertThrows( WorkspaceManagerException.class, () -> wsmDao.getBlobStorageUrl(WORKSPACE_UUID.toString(), BEARER_TOKEN)); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, thrown.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, thrown.getStatusCode()); } @Test diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/retry/RestClientRetryTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/retry/RestClientRetryTest.java index 7071bb90d..9d756e0c1 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/retry/RestClientRetryTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/retry/RestClientRetryTest.java @@ -304,7 +304,7 @@ private void expectRestExceptionWithStatusCode(int expectedStatusCode, RestCall< assertEquals( expectedStatusCode, - actual.getRawStatusCode(), + actual.getStatusCode().value(), "RestCall: Incorrect status code in RestException"); } @@ -318,7 +318,7 @@ private void expectRestExceptionWithStatusCode( assertEquals( expectedStatusCode, - actual.getRawStatusCode(), + actual.getStatusCode().value(), "voidRestCall: Incorrect status code in RestException"); } } diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/service/InstanceServiceSamExceptionTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/service/InstanceServiceSamExceptionTest.java index 475bb36bb..8aa70e3b7 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/service/InstanceServiceSamExceptionTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/service/InstanceServiceSamExceptionTest.java @@ -250,7 +250,7 @@ private void doSamCreateTest(UUID instanceId, int expectedSamExceptionCode) { "createInstance should throw if caller does not have permission to create wds-instance resource in Sam"); assertEquals( expectedSamExceptionCode, - samException.getRawStatusCode(), + samException.getStatusCode().value(), "RestException from createInstance should have same status code as the thrown ApiException"); List allInstances = instanceService.listInstances(VERSION); assertFalse(allInstances.contains(instanceId), "should not have created the instances."); @@ -270,7 +270,7 @@ private void doSamDeleteTest(UUID instanceId, int expectedSamExceptionCode) { "deleteInstance should throw if caller does not have permission to create wds-instance resource in Sam"); assertEquals( expectedSamExceptionCode, - samException.getRawStatusCode(), + samException.getStatusCode().value(), "RestException from deleteInstance should have same status code as the thrown ApiException"); allInstances = instanceService.listInstances(VERSION); assertTrue( diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/service/RecordOrchestratorServiceTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/service/RecordOrchestratorServiceTest.java index 24679f3d7..274328462 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/service/RecordOrchestratorServiceTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/service/RecordOrchestratorServiceTest.java @@ -168,7 +168,7 @@ void testValidateVersion() { ResponseStatusException.class, () -> validateVersion("invalidVersion"), "validateVersion should have thrown an error"); - assert (e.getStatus().equals(HttpStatus.BAD_REQUEST)); + assert (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)); } private void testCreateRecord(String newRecordId, String testKey, String testVal) { diff --git a/service/src/test/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerDaoTest.java b/service/src/test/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerDaoTest.java index 5dba73c3a..a6f7fd6d8 100644 --- a/service/src/test/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerDaoTest.java +++ b/service/src/test/java/org/databiosphere/workspacedataservice/workspacemanager/WorkspaceManagerDaoTest.java @@ -94,7 +94,7 @@ void testErrorThrown() throws ApiException { assertThrows( WorkspaceManagerException.class, () -> workspaceManagerDao.linkSnapshotForPolicy(testSnapshot)); - assertEquals(statusCode, exception.getRawStatusCode()); + assertEquals(statusCode, exception.getStatusCode().value()); } @Test diff --git a/service/src/test/resources/logback-spring.xml b/service/src/test/resources/logback-spring.xml index b064be6b2..427b65e45 100644 --- a/service/src/test/resources/logback-spring.xml +++ b/service/src/test/resources/logback-spring.xml @@ -1,28 +1,22 @@ - - - - %green(%d{ISO8601}) %highlight(%-5level) [%blue(%X{requestId})] %yellow(%C{1.}): %msg%n%throwable - - - + + - - - - + + + + - - - - + + + + - - - - + + + +