Skip to content

Commit

Permalink
Merge pull request #1204 from navikt/oppgave-client
Browse files Browse the repository at this point in the history
Oppgave client
  • Loading branch information
oyvind-wedoe authored Sep 3, 2024
2 parents f622fc4 + 6b9e391 commit 85480b1
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 9 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ val tikaVersion = "2.9.2"
val verapdfVersion = "1.26.1"
val klageKodeverkVersion = "1.8.37"
val commonsFileupload2JakartaVersion = "2.0.0-M1"
val otelVersion = "1.40.0"

plugins {
val kotlinVersion = "2.0.0"
Expand Down Expand Up @@ -66,6 +67,8 @@ dependencies {
implementation("io.micrometer:micrometer-registry-prometheus")
implementation("io.micrometer:micrometer-tracing-bridge-brave")

implementation("io.opentelemetry:opentelemetry-api:$otelVersion")

implementation("net.javacrumbs.shedlock:shedlock-spring:$shedlockVersion")
implementation("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:$shedlockVersion")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ import no.nav.klage.oppgave.clients.norg2.Norg2Client
import no.nav.klage.oppgave.clients.pdl.PdlFacade
import no.nav.klage.oppgave.clients.pdl.Person
import no.nav.klage.oppgave.domain.klage.*
import no.nav.klage.oppgave.service.DokDistKanalService
import no.nav.klage.oppgave.service.KodeverkService
import no.nav.klage.oppgave.service.RegoppslagService
import no.nav.klage.oppgave.service.SaksbehandlerService
import no.nav.klage.oppgave.service.*
import no.nav.klage.oppgave.util.getLogger
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand All @@ -34,6 +31,7 @@ class BehandlingMapper(
private val kodeverkService: KodeverkService,
private val regoppslagService: RegoppslagService,
private val dokDistKanalService: DokDistKanalService,
private val oppgaveApiService: OppgaveApiService,
) {

companion object {
Expand Down Expand Up @@ -104,6 +102,7 @@ class BehandlingMapper(
saksbehandler = klagebehandling.toSaksbehandlerView(),
previousSaksbehandler = klagebehandling.toPreviousSaksbehandlerView(),
varsletFrist = klagebehandling.varsletFrist,
oppgavebeskrivelse = getOppgavebeskrivelse(klagebehandling.oppgaveId),
)
}

Expand Down Expand Up @@ -206,6 +205,7 @@ class BehandlingMapper(
saksbehandler = ankebehandling.toSaksbehandlerView(),
previousSaksbehandler = ankebehandling.toPreviousSaksbehandlerView(),
varsletFrist = ankebehandling.varsletFrist,
oppgavebeskrivelse = getOppgavebeskrivelse(ankebehandling.oppgaveId),
)
}

Expand Down Expand Up @@ -259,9 +259,18 @@ class BehandlingMapper(
saksbehandler = ankeITrygderettenbehandling.toSaksbehandlerView(),
previousSaksbehandler = ankeITrygderettenbehandling.toPreviousSaksbehandlerView(),
varsletFrist = null,
oppgavebeskrivelse = getOppgavebeskrivelse(ankeITrygderettenbehandling.oppgaveId),
)
}

private fun getOppgavebeskrivelse(oppgaveId: Long?): String? {
return if (oppgaveId == null) {
null
} else {
oppgaveApiService.getOppgaveEntryView(oppgaveId)
}
}

fun getSakenGjelderView(sakenGjelder: SakenGjelder): BehandlingDetaljerView.SakenGjelderView {
if (sakenGjelder.erPerson()) {
val person = pdlFacade.getPersonInfo(sakenGjelder.partId.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ data class BehandlingDetaljerView(
val saksbehandler: SaksbehandlerView?,
val previousSaksbehandler: SaksbehandlerView?,
val varsletFrist: LocalDate?,
val oppgavebeskrivelse: String?,
) {

data class CombinedMedunderskriverAndROLView(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.nav.klage.oppgave.clients.oppgaveapi

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

class OffsetDateTimeToLocalDateTimeDeserializer : StdDeserializer<LocalDateTime>(LocalDateTime::class.java) {

override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext?): LocalDateTime {
return LocalDateTime.parse(jsonParser.readValueAs(String::class.java), DateTimeFormatter.ISO_OFFSET_DATE_TIME)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package no.nav.klage.oppgave.clients.oppgaveapi

import no.nav.klage.oppgave.util.TokenUtil
import org.springframework.beans.factory.annotation.Value
import org.springframework.web.reactive.function.client.WebClient
import io.opentelemetry.api.trace.Span
import no.nav.klage.oppgave.util.getLogger
import no.nav.klage.oppgave.util.getSecureLogger

import org.springframework.http.HttpHeaders
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClientResponseException

import org.springframework.web.reactive.function.client.bodyToMono

@Component
class OppgaveApiClient(
private val oppgaveApiWebClient: WebClient,
private val tokenUtil: TokenUtil,
@Value("\${spring.application.name}") private val applicationName: String,
){

companion object {
@Suppress("JAVA_CLASS_ON_COMPANION")
private val logger = getLogger(javaClass.enclosingClass)
private val securelogger = getSecureLogger()
}

fun getOppgave(oppgaveId: Long): OppgaveApiRecord {
return logTimingAndWebClientResponseException(OppgaveApiClient::getOppgave.name) {
oppgaveApiWebClient.get()
.uri { uriBuilder ->
uriBuilder.pathSegment("oppgaver", "{id}").build(oppgaveId)
}
.header(
HttpHeaders.AUTHORIZATION,
"Bearer ${tokenUtil.getSaksbehandlerAccessTokenWithOppgaveApiScope()}"
)
.header("X-Correlation-ID", Span.current().spanContext.traceId)
.header("Nav-Consumer-Id", applicationName)
.retrieve()
.bodyToMono<OppgaveApiRecord>()
.block() ?: throw OppgaveClientException("Oppgave could not be fetched")
}
}

private fun <T> logTimingAndWebClientResponseException(methodName: String, function: () -> T): T {
val start: Long = System.currentTimeMillis()
try {
return function.invoke()
} catch (ex: WebClientResponseException) {
logger.warn("Caught WebClientResponseException, see securelogs for details")
securelogger.error(
"Got a {} error calling Oppgave {} {} with message {}",
ex.statusCode,
ex.request?.method ?: "-",
ex.request?.uri ?: "-",
ex.responseBodyAsString
)
throw OppgaveClientException("Caught WebClientResponseException", ex)
} catch (rtex: RuntimeException) {
logger.warn("Caught RuntimeException", rtex)
throw OppgaveClientException("Caught runtimeexception", rtex)
} finally {
val end: Long = System.currentTimeMillis()
logger.info("Method {} took {} millis", methodName, (end - start))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package no.nav.klage.oppgave.clients.oppgaveapi

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import java.time.LocalDate
import java.time.LocalDateTime

@JsonIgnoreProperties(ignoreUnknown = true)
data class OppgaveApiRecord(
val id: Long,
val versjon: Int,
val journalpostId: String?,
val saksreferanse: String?,
val mappeId: Long?,
val status: Status?,
val tildeltEnhetsnr: String?,
val opprettetAvEnhetsnr: String?,
val endretAvEnhetsnr: String?,
val tema: String,
val temagruppe: String?,
val behandlingstema: String?,
val oppgavetype: String?,
val behandlingstype: String?,
val prioritet: Prioritet?,
val tilordnetRessurs: String?,
val beskrivelse: String?,
val fristFerdigstillelse: LocalDate?,
val aktivDato: String?,
val opprettetAv: String?,
val endretAv: String?,
@JsonDeserialize(using = OffsetDateTimeToLocalDateTimeDeserializer::class)
val opprettetTidspunkt: LocalDateTime,
@JsonDeserialize(using = OffsetDateTimeToLocalDateTimeDeserializer::class)
val endretTidspunkt: LocalDateTime?,
@JsonDeserialize(using = OffsetDateTimeToLocalDateTimeDeserializer::class)
val ferdigstiltTidspunkt: LocalDateTime?,
val behandlesAvApplikasjon: String?,
val journalpostkilde: String?,
val identer: List<Ident>?,
val metadata: Map<String, String>?,
val bnr: String?,
val samhandlernr: String?,
val aktoerId: String?,
val orgnr: String?,
)

enum class Status(val statusId: Long) {

OPPRETTET(1),
AAPNET(2),
UNDER_BEHANDLING(3),
FERDIGSTILT(4),
FEILREGISTRERT(5);

companion object {

fun of(statusId: Long): Status {
return entries.firstOrNull { it.statusId == statusId }
?: throw IllegalArgumentException("No status with $statusId exists")
}

fun kategoriForStatus(status: Status): Statuskategori {
return when (status) {
AAPNET, OPPRETTET, UNDER_BEHANDLING -> Statuskategori.AAPEN
FEILREGISTRERT, FERDIGSTILT -> Statuskategori.AVSLUTTET
}
}
}

fun kategoriForStatus(): Statuskategori {
return kategoriForStatus(this)
}
}

enum class Prioritet {
HOY,
NORM,
LAV
}

data class Ident(
val id: Long?,
val identType: IdentType,
val verdi: String,
val folkeregisterident: String?,
val registrertDato: LocalDate?
)

enum class IdentType {
AKTOERID, ORGNR, SAMHANDLERNR, BNR
}

enum class Statuskategori {
AAPEN,
AVSLUTTET;

fun statuserForKategori(kategori: Statuskategori): List<Status> {
return when (kategori) {
AAPEN -> aapen()
AVSLUTTET -> avsluttet()
}
}

fun avsluttet(): List<Status> {
return listOf(Status.FERDIGSTILT, Status.FEILREGISTRERT)
}

fun aapen(): List<Status> {
return listOf(Status.OPPRETTET, Status.AAPNET, Status.UNDER_BEHANDLING)
}
}

data class OppgaveResponse(
val antallTreffTotalt: Int,
val oppgaver: List<OppgaveApiRecord>
)

class OppgaveClientException : Exception {
constructor(message: String?) : super(message)

constructor(message: String?, cause: Throwable?) : super(message, cause)
}

data class FerdigstillOppgaveRequest(
val oppgaveId: Long,
val versjon: Int,
val status: Status = Status.FERDIGSTILT,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package no.nav.klage.oppgave.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient.newConnection

@Configuration
class OppgaveApiClientConfiguration(private val webClientBuilder: WebClient.Builder) {
@Value("\${OPPGAVE_API_BASE_URL}")
private lateinit var oppgaveBaseURL: String

@Bean
fun oppgaveApiWebClient(): WebClient {
return webClientBuilder
.baseUrl(oppgaveBaseURL)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.clientConnector(ReactorClientHttpConnector(newConnection()))
.build()
}

}
24 changes: 24 additions & 0 deletions src/main/kotlin/no/nav/klage/oppgave/service/OppgaveApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package no.nav.klage.oppgave.service

import no.nav.klage.oppgave.clients.oppgaveapi.OppgaveApiClient
import no.nav.klage.oppgave.util.getLogger
import no.nav.klage.oppgave.util.getSecureLogger
import org.springframework.stereotype.Service

@Service
class OppgaveApiService(
private val oppgaveApiClient: OppgaveApiClient
) {

companion object {
@Suppress("JAVA_CLASS_ON_COMPANION")
private val logger = getLogger(javaClass.enclosingClass)
private val securelogger = getSecureLogger()
}

fun getOppgaveEntryView(oppgaveId: Long): String? {
val oppgave = oppgaveApiClient.getOppgave(oppgaveId = oppgaveId)
return oppgave.beskrivelse
}

}
6 changes: 6 additions & 0 deletions src/main/kotlin/no/nav/klage/oppgave/util/TokenUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ class TokenUtil(
return response.accessToken!!
}

fun getSaksbehandlerAccessTokenWithOppgaveApiScope(): String {
val clientProperties = clientConfigurationProperties.registration["oppgave-api-onbehalfof"]!!
val response = oAuth2AccessTokenService.getAccessToken(clientProperties)
return response.accessToken!!
}

fun getAccessTokenFrontendSent(): String =
tokenValidationContextHolder.getTokenValidationContext().getJwtToken(SecurityConfiguration.ISSUER_AAD)!!.encodedToken

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-dev-gcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ ARBEID_OG_INNTEKT_URL: https://arbeid-og-inntekt-q2.dev-fss-pub.nais.io/

KLAGE_FSS_PROXY_URL: https://klage-fss-proxy.dev-fss-pub.nais.io

OPPGAVE_API_BASE_URL: https://oppgave.dev-fss-pub.nais.io/api/v1
OPPGAVE_API_SCOPE: dev-fss.oppgavehandtering.oppgave

FSS_CLUSTER: dev-fss

SETTINGS_CLEANUP_CRON: 0 0 12 * * MON
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-prod-gcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ ARBEID_OG_INNTEKT_URL: https://arbeid-og-inntekt.prod-fss-pub.nais.io/

KLAGE_FSS_PROXY_URL: https://klage-fss-proxy.prod-fss-pub.nais.io

OPPGAVE_API_BASE_URL: https://oppgave.prod-fss-pub.nais.io/api/v1
OPPGAVE_API_SCOPE: prod-fss.oppgavehandtering.oppgave

FSS_CLUSTER: prod-fss

SETTINGS_CLEANUP_CRON: 0 15 16 * * WED
Loading

0 comments on commit 85480b1

Please sign in to comment.