Skip to content

Commit

Permalink
Merged in classes from IOPS-Utilty repository
Browse files Browse the repository at this point in the history
This is used to cache IG's in AWS S3 to reduce application start up time
  • Loading branch information
KevinMayfield committed Jan 16, 2025
1 parent 1dfb33a commit 943c2a8
Show file tree
Hide file tree
Showing 8 changed files with 761 additions and 4 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
<artifactId>jackson-module-kotlin</artifactId>
<version>${jackson_databind_version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.3.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class FHIRR4RestfulServer(
private val valueSetProvider: ValueSetProvider,
private val codeSystemProvider: CodeSystemProvider,
private val compostionProvider: CompostionProvider,
private val igCacheProvider: ImplementationGuideProvider,
private val binaryProvider: BinaryProvider,
@Qualifier("SupportChain") private val supportChain: IValidationSupport,
val fhirServerProperties: FHIRServerProperties,
private val messageProperties: MessageProperties
Expand All @@ -67,8 +69,8 @@ class FHIRR4RestfulServer(
registerProvider(valueSetProvider)
registerProvider(codeSystemProvider)
registerProvider(compostionProvider)


registerProvider(igCacheProvider)
registerProvider(binaryProvider)

registerInterceptor(CapabilityStatementInterceptor(this.fhirContext, fhirPackage, supportChain, fhirServerProperties))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package uk.nhs.england.fhirvalidator.awsProvider

import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.rest.api.MethodOutcome
import ca.uhn.fhir.rest.client.api.IGenericClient
import org.hl7.fhir.instance.model.api.IBaseBundle
import org.hl7.fhir.r4.model.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import uk.nhs.england.fhirvalidator.configuration.FHIRServerProperties
import uk.nhs.england.fhirvalidator.configuration.MessageProperties
import uk.nhs.england.fhirvalidator.interceptor.CognitoAuthInterceptor
import java.io.File
import java.nio.file.Files
import java.util.*

@Component
class AWSBinary(val messageProperties: MessageProperties, val awsClient: IGenericClient,
//sqs: AmazonSQS?,
@Qualifier("R4") val ctx: FhirContext,
val fhirServerProperties: FHIRServerProperties,
private val cognitoAuthInterceptor: CognitoAuthInterceptor
) {

private val log = LoggerFactory.getLogger("FHIRAudit")

fun get(url : String): ImplementationGuide? {
var bundle: Bundle? = null
var retry = 3
while (retry > 0) {
try {
bundle = awsClient
.search<IBaseBundle>()
.forResource(ImplementationGuide::class.java)
.where(
ImplementationGuide.URL.matches().value(url)
)
.returnBundle(Bundle::class.java)
.execute()
break
} catch (ex: Exception) {
// do nothing
log.error(ex.message)
retry--
if (retry == 0) throw ex
}
}
if (bundle == null || !bundle.hasEntry()) return null
return bundle.entryFirstRep.resource as ImplementationGuide
}

fun get(internalId: IdType): Binary {
val binary = Binary()
var path = internalId.value
if (!path.startsWith("/")) path = "/" + path
val location = cognitoAuthInterceptor.getBinaryLocation(path)
println(location.getString("presignedGetUrl"))
binary.id = location.getString("id")
binary.contentType = location.getString("contentType")
val conn = cognitoAuthInterceptor.getBinary(location.getString("presignedGetUrl"))
binary.data = conn.inputStream.readAllBytes()
return binary
}

public fun create(fileName : String): MethodOutcome? {

var response: MethodOutcome? = null
val binary = Binary()
binary.contentType = "application/gzip"
val json = cognitoAuthInterceptor.postBinaryLocation(binary)
val location = json.getString("presignedPutUrl")
binary.id = json.getString("id")
var file = File(fileName)
val fileContent: ByteArray = Files.readAllBytes(file.toPath())
cognitoAuthInterceptor.postBinary(location,fileContent)
return MethodOutcome().setResource(binary)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package uk.nhs.england.fhirvalidator.awsProvider

import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.rest.api.MethodOutcome
import ca.uhn.fhir.rest.client.api.IGenericClient
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException
import org.hl7.fhir.instance.model.api.IBaseBundle
import org.hl7.fhir.r4.model.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import uk.nhs.england.fhirvalidator.configuration.FHIRServerProperties
import uk.nhs.england.fhirvalidator.configuration.MessageProperties
import java.util.*

@Component
class AWSImplementationGuide(val messageProperties: MessageProperties, val awsClient: IGenericClient,
//sqs: AmazonSQS?,
@Qualifier("R4") val ctx: FhirContext,
val fhirServerProperties: FHIRServerProperties,
val awsAuditEvent: AWSAuditEvent
) {

private val log = LoggerFactory.getLogger("FHIRAudit")


fun createUpdate(newImplementationGuide: ImplementationGuide): ImplementationGuide? {
var awsBundle: Bundle? = null
if (!newImplementationGuide.hasUrl()) throw UnprocessableEntityException("ImplementationGuide has no identifier")
var nhsIdentifier = newImplementationGuide.url
if (nhsIdentifier == null) throw UnprocessableEntityException("ImplementationGuide has no identifier")
var retry = 3
while (retry > 0) {
try {

awsBundle = awsClient!!.search<IBaseBundle>().forResource(ImplementationGuide::class.java)
.where(
ImplementationGuide.URL.matches().value(nhsIdentifier)
)
.returnBundle(Bundle::class.java)
.execute()
break
} catch (ex: Exception) {
// do nothing
log.error(ex.message)
retry--
if (retry == 0) throw ex
}
}


// This v3esquw data should have been processed into propoer resources so remove
newImplementationGuide.contained = ArrayList()

if (awsBundle!!.hasEntry() && awsBundle.entryFirstRep.hasResource()
&& awsBundle.entryFirstRep.hasResource()
&& awsBundle.entryFirstRep.resource is ImplementationGuide
) {
val diagnosticReport = awsBundle.entryFirstRep.resource as ImplementationGuide
// Dont update for now - just return aws ImplementationGuide
return update(diagnosticReport, newImplementationGuide)!!.resource as ImplementationGuide
} else {
return create(newImplementationGuide)!!.resource as ImplementationGuide
}
}

public fun get(url : String): ImplementationGuide? {
var bundle: Bundle? = null
var retry = 3
while (retry > 0) {
try {
bundle = awsClient
.search<IBaseBundle>()
.forResource(ImplementationGuide::class.java)
.where(
ImplementationGuide.URL.matches().value(url)
)
.returnBundle(Bundle::class.java)
.execute()
break
} catch (ex: Exception) {
// do nothing
log.error(ex.message)
retry--
if (retry == 0) throw ex
}
}
if (bundle == null || !bundle.hasEntry()) return null
return bundle.entryFirstRep.resource as ImplementationGuide
}

private fun update(implementationGuide: ImplementationGuide, newImplementationGuide: ImplementationGuide): MethodOutcome? {
var response: MethodOutcome? = null
var changed: Boolean

// TODO do change detection
changed = true

if (!changed) return MethodOutcome().setResource(implementationGuide)
var retry = 3
while (retry > 0) {
try {
newImplementationGuide.id = implementationGuide.idElement.value
response = awsClient!!.update().resource(newImplementationGuide).withId(implementationGuide.id).execute()
log.info("AWS ImplementationGuide updated " + response.resource.idElement.value)
val auditEvent = awsAuditEvent.createAudit(implementationGuide, AuditEvent.AuditEventAction.C)
awsAuditEvent.writeAWS(auditEvent)
break
} catch (ex: Exception) {
// do nothing
log.error(ex.message)
retry--
if (retry == 0) throw ex
}
}
return response

}

private fun create(newImplementationGuide: ImplementationGuide): MethodOutcome? {

var response: MethodOutcome? = null

var retry = 3
while (retry > 0) {
try {
response = awsClient
.create()
.resource(newImplementationGuide)
.execute()
val diagnosticReport = response.resource as ImplementationGuide
val auditEvent = awsAuditEvent.createAudit(diagnosticReport, AuditEvent.AuditEventAction.C)
awsAuditEvent.writeAWS(auditEvent)
break
} catch (ex: Exception) {
// do nothing
log.error(ex.message)
retry--
if (retry == 0) throw ex
}
}
return response
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,76 @@ open class OpenApiConfig(@Qualifier("R4") val ctx : FhirContext,
)
oas.path("/FHIR/R4/CodeSystem/\$subsumes",subsumesItem)
}
oas.path("/FHIR/R4/ImplementationGuide/\$cacheIG", PathItem()
.get(
Operation()
.addTagsItem(EXPERIMENTAL)
.summary("Retrieves an IG and triggers snapshot building process").responses(getApiResponses()))
.addParametersItem(
Parameter()
.name("name")
.`in`("query")
.required(true)
.style(Parameter.StyleEnum.SIMPLE)
.description("The name or package id")
.schema(StringSchema())
.example("uk.nhsdigital.r4")
)
.addParametersItem(
Parameter()
.name("version")
.`in`("query")
.required(true)
.style(Parameter.StyleEnum.SIMPLE)
.description("The version of the package")
.schema(StringSchema())
.example("2.6.0")
)
)


val implementationGuideItem = PathItem()
.get(
Operation()
.addTagsItem(EXPERIMENTAL)
.summary(" Option Search Parameters")
.responses(getApiResponses())
.addParametersItem(
Parameter()
.name("url")
.`in`("query")
.required(false)
.style(Parameter.StyleEnum.SIMPLE)
.description("The ID of the resource")
.schema(StringSchema())
.example("https://fhir.nhs.uk/ImplementationGuide/uk.nhsdigital.r4-2.6.0")
)

)

oas.path("/FHIR/R4/ImplementationGuide",implementationGuideItem)


var binaryItem = PathItem()
.get(
Operation()
.addTagsItem(EXPERIMENTAL)
.summary("Read Binary. This returns the raw implementation guide")
.responses(getApiResponsesBinary())
.addParametersItem(Parameter()
.name("url")
.`in`("query")
.required(true)
.style(Parameter.StyleEnum.SIMPLE)
.description("url of the ImplementationGuide")
.schema(StringSchema())
.example("https://fhir.nhs.uk/ImplementationGuide/fhir.r4.ukcore.stu1-0.5.1")
)
)



oas.path("/FHIR/R4/ImplementationGuide/\$package",binaryItem)
if (servicesProperties.LOINC) {
oas.addTagsItem(io.swagger.v3.oas.models.tags.Tag()
.name(LOINC)
Expand Down Expand Up @@ -1066,7 +1136,16 @@ open class OpenApiConfig(@Qualifier("R4") val ctx : FhirContext,
val apiResponses = ApiResponses().addApiResponse("200",response200)
return apiResponses
}
fun getApiResponsesBinary() : ApiResponses {

val response200 = ApiResponse()
response200.description = "OK"
val exampleList = mutableListOf<Example>()
exampleList.add(Example().value("{}"))
response200.content = Content().addMediaType("*/*", MediaType().schema(StringSchema()._default("{}")))
val apiResponses = ApiResponses().addApiResponse("200",response200)
return apiResponses
}


fun getApiResponsesRAWJSON() : ApiResponses {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package uk.nhs.england.fhirvalidator.provider

import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.rest.annotation.*
import ca.uhn.fhir.rest.server.IResourceProvider
import jakarta.servlet.http.HttpServletRequest
import mu.KLogging
import org.hl7.fhir.r4.model.*
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import uk.nhs.england.fhirvalidator.awsProvider.AWSBinary
import uk.nhs.england.fhirvalidator.interceptor.CognitoAuthInterceptor

@Component
class BinaryProvider(
private val awsBinary: AWSBinary,

) : IResourceProvider {
companion object : KLogging()

override fun getResourceType(): Class<Binary> {
return Binary::class.java
}

@Read
fun read(httpRequest : HttpServletRequest, @IdParam internalId: IdType): Binary? {
return awsBinary.get(internalId)
}

}
Loading

0 comments on commit 943c2a8

Please sign in to comment.