diff --git a/docker-compose.yml b/docker-compose.yml index d9e2368..88a6d17 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,12 +19,14 @@ services: - API_BASE_URL_JOBSBOARD=https://jobs-board-api-dev.hmpps.service.justice.gov.uk - API_BASE_URL_MNJOBBOARD=https://testservices.sequation.net/sequation-job-api - MN_JOBBOARD_API_TOKEN= + - API_CLIENT_ID= + - API_CLIENT_SECRET= hmpps-auth: image: quay.io/hmpps/hmpps-auth:latest networks: - hmpps - container_name: hmpps-auth + container_name: integration-hmpps-auth ports: - "8090:8080" healthcheck: diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/JpaConfig.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/JpaConfig.kt new file mode 100644 index 0000000..b95062a --- /dev/null +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/JpaConfig.kt @@ -0,0 +1,8 @@ +package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.jpa.repository.config.EnableJpaAuditing + +@Configuration +@EnableJpaAuditing +class JpaConfig diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/WebClientConfiguration.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/WebClientConfiguration.kt index 2748fff..6b9437e 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/WebClientConfiguration.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/config/WebClientConfiguration.kt @@ -1,10 +1,14 @@ package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config +import io.netty.handler.logging.LogLevel import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.client.reactive.ReactorClientHttpConnector import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager import org.springframework.web.reactive.function.client.WebClient +import reactor.netty.http.client.HttpClient +import reactor.netty.transport.logging.AdvancedByteBufFormat import uk.gov.justice.hmpps.kotlin.auth.authorisedWebClient import uk.gov.justice.hmpps.kotlin.auth.healthWebClient import java.time.Duration @@ -15,6 +19,7 @@ class WebClientConfiguration( @Value("\${hmpps-auth.url}") val hmppsAuthBaseUri: String, @Value("\${api.health-timeout:2s}") val healthTimeout: Duration, @Value("\${api.timeout:20s}") val timeout: Duration, + @Value("\${api.client.logging.enabled:false}") val clientLogging: Boolean, ) { // HMPPS Auth health ping is required if your service calls HMPPS Auth to get a token to call other services // TODO: Remove the health ping if no call outs to other services are made @@ -36,7 +41,14 @@ class WebClientConfiguration( authorizedClientManager: OAuth2AuthorizedClientManager, builder: WebClient.Builder, @Value("\${api.base.url.jobsboard}") jobsboardApiBaseUri: String, - ): WebClient = builder.authorisedWebClient(authorizedClientManager, registrationId = "hmpps-jobs-board-api", url = jobsboardApiBaseUri, timeout) + ): WebClient = builder.apply { + if (clientLogging) it.clientConnector(clientConnectorWithLogging()) + }.authorisedWebClient( + authorizedClientManager, + registrationId = "hmpps-jobs-board-api", + url = jobsboardApiBaseUri, + timeout, + ) @ConditionalOnIntegrationEnabled @Bean @@ -44,5 +56,15 @@ class WebClientConfiguration( builder: WebClient.Builder, @Value("\${api.base.url.mnjobboard}") mnjobboardApiBaseUri: String, @Value("\${mn.jobboard.api.token}") mnJobBoardToken: String, - ): WebClient = builder.defaultHeader("Authorization", "Bearer $mnJobBoardToken").baseUrl(mnjobboardApiBaseUri).build() + ): WebClient = builder.defaultHeader("Authorization", "Bearer $mnJobBoardToken").baseUrl(mnjobboardApiBaseUri).apply { + if (clientLogging) it.clientConnector(clientConnectorWithLogging()) + }.build() + + private fun clientConnectorWithLogging() = ReactorClientHttpConnector(httpClientWireTapLogging()) + + private fun httpClientWireTapLogging() = HttpClient.create().wiretap( + "reactor.netty.http.client.HttpClient", + LogLevel.DEBUG, + AdvancedByteBufFormat.TEXTUAL, + ) } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/JobsBoardApiWebClient.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/JobsBoardApiWebClient.kt index c0b466b..80f6cd6 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/JobsBoardApiWebClient.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/JobsBoardApiWebClient.kt @@ -28,8 +28,9 @@ class JobsBoardApiWebClient( return jobsBoardWebClient .get().uri("/employers/{id}", id).accept(APPLICATION_JSON).retrieve() .bodyToMono(GetEmployerResponse::class.java) - .onErrorResume(WebClientResponseException.NotFound::class.java) { - log.debug("Employer not found. employerId={}", id) + .onErrorResume(WebClientResponseException.NotFound::class.java) { error -> + val errorResponse = if (error is WebClientResponseException) error.responseBodyAsString else null + log.warn("Employer not found. employerId={}; errorResponse={}", id, errorResponse) Mono.empty() }.block()?.employer() } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/MNJobBoardApiWebClient.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/MNJobBoardApiWebClient.kt index 5221996..48d5d40 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/MNJobBoardApiWebClient.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/jobsboardintegrationapi/shared/infrastructure/MNJobBoardApiWebClient.kt @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.http.MediaType.APPLICATION_JSON import org.springframework.stereotype.Service import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.WebClientResponseException import reactor.core.publisher.Mono import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config.ConditionalOnIntegrationEnabled import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.domain.MNJobBoardApiClient @@ -23,9 +24,15 @@ class MNJobBoardApiWebClient( override fun createEmployer(request: CreatEmployerRequest): CreatEmployerResponse { log.debug("Creating employer employerName={}", request.employerName) - return mnJobBoardWebClient.post().uri(EMPLOYERS_ENDPOINT).accept(APPLICATION_JSON).retrieve() + log.trace("Create employer request={}", request) + return mnJobBoardWebClient.post().uri(EMPLOYERS_ENDPOINT) + .accept(APPLICATION_JSON).body(Mono.just(request), request.javaClass) + .retrieve() .bodyToMono(MNCreatEmployerResponse::class.java) - .onErrorResume { error -> Mono.error(Exception("Fail to create employer", error)) }.block()!! + .onErrorResume { error -> + val errorResponse = if (error is WebClientResponseException) error.responseBodyAsString else null + Mono.error(Exception("Fail to create employer! errorResponse=$errorResponse", error)) + }.block()!! .responseObject } } diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..d63d31c --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,23 @@ +{ + "properties": [ + { + "name": "api.base.url.jobsboard", + "type": "java.lang.String", + "description": "Base URL for Jobs Board APIs" + }, + { + "name": "api.base.url.mnjobboard", + "type": "java.lang.String", + "description": "Base URL for MN Job Board APIs" + }, + { + "name": "api.integration.enabled", + "type": "java.lang.String", + "description": "Feature flag to switch on/off JobsBoard-to-MN-Job-Board Integration functions" + }, + { + "name": "api.client.logging.enabled", + "type": "java.lang.String", + "description": "Enabling client logging at API Web Client; disabled by default" + } + ] } \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 4a20ca3..c844161 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -13,9 +13,10 @@ api: base.url: jobsboard: "https://jobs-board-api-dev.hmpps.service.justice.gov.uk" mnjobboard: "https://testservices.sequation.net/sequation-job-api" - integration: enabled: false + client.logging: + enabled: false spring: datasource: @@ -25,3 +26,8 @@ spring: flyway: user: 'job-board-intg' password: 'job-board-intg' + +logging.level: + uk.gov.justice.digital.hmpps.jobsboardintegrationapi: + shared.infrastructure: DEBUG + reactor.netty.http.client: INFO \ No newline at end of file