Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update NVI on upload #2

Merged
merged 4 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 17 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ source software licensed under the business-friendly [Apache Software License 2.
### JPA server
The HAPI FHIR JPA server offers a complete RESTful FHIR server implementation using JPA 2.0 for database persistence. This project is build up from a [starter repository](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) to deploy an FHIR server, the full source can be found [here](https://github.com/hapifhir/hapi-fhir).
In the extensive [DOCS](https://hapifhir.io/hapi-fhir/docs/getting_started/introduction.html) much is explained about the JPA server, how it works and how it can be extended for particular use-cases. The project does not provide any security or enterprise audit logging.

**[HAPI JPA Server](https://hapifhir.io/hapi-fhir/docs/server_jpa/architecture.html) has the following components:** ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXdL7rKrfOpq7myofkaXeSW-Pv1i7pHQe53rX1v61pa_ls7IC6UL2JIKZ5Xtd8OEB31l3NRfjLBQvSW0qinkDcz-Jw500DjTRkHicwuIv3GzNl6Y3Ldz3mtII8FA-Arq2v1VLKxPWk_HKutRQL6C6j7AU41G?key=flRIJ62mIRWJ10ofPwigtw)
**[HAPI JPA Server](https://hapifhir.io/hapi-fhir/docs/server_jpa/architecture.html) has the following components:** !
- **[Resource Providers](https://hapifhir.io/hapi-fhir/docs/server_plain/resource_providers.html):** A RESTful server Resource Provider is provided for each resource type in a given release of FHIR. Each resource provider implements all the FHIR methods, such as Search, Read, Create, Delete, etc.
- **[HAPI DAOs](https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-storage/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.html):** The Data Access Objects (_DAO_) implement all the database business logic relating to the storage, indexing, and retrieval of FHIR resources, using the underlying JPA API.
- **[Database](https://hapifhir.io/hapi-fhir/docs/server_jpa/database_support.html):** The RESTful server uses an embedded database but can be configured to communicate with any database supported by [Hibernate](https://hibernate.org/orm/). The JPA Server maintains active support for several databases such as: [MS SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads), [PostgreSQL](https://www.postgresql.org/), [ORACLE](https://www.oracle.com/database/).
Expand Down Expand Up @@ -56,26 +55,25 @@ To get started:


_To test the HAPI with the [Timeline](https://github.com/minvws/nl-irealisatie-zmodules-pgo-demo) service,
add at least an ImagingStudy to the HAPI fhir database._
at least one ImagingStudy has to be added to the HAPI database._

**To easily set up sample FHIR entries use the following steps:**
1. Run JPA server
2. Open new terminal in project directory
3. Run the following command to get a bundle with sample data loaded in:

```curl
curl -X POST http://localhost:8080/fhir \
-H "Content-Type: application/json" \
--data @fhir_examples/bundle.json
1. Start the JPA server as shown above
2. Start all the needed modules as shown in [GFModules project](https://github.com/minvws/gfmodules-coordination)
3. Open new terminal in project directory
4. Run the following command to get a bundle with sample data loaded in:

```bash
sh create_patient.sh
```

4. Test whether the HAPI has successfully gotten the data:
5. Test whether the HAPI has successfully gotten the data:

```curl
curl -X GET http://localhost:8080/fhir/Patient/3
```

5. It should return something like:
6. It should return something like:
```json
{
"resourceType": "Patient",
Expand All @@ -99,39 +97,13 @@ curl -X GET http://localhost:8080/fhir/Patient/3
}
```

Before you request the timeline service ensure your endpoint is registered in the [address](https://github.com/minvws/nl-irealisatie-zmodules-addressing-register) and
the [pseudonym-exchange-service](https://github.com/minvws/nl-irealisatie-zmodules-pseudonym-service) knows your provider-id.
To find your provider-id, look in the [settings file](src/main/resources/application.yaml).

In the address DB you should have a entry that sets the address endpoint to: http://host.docker.internal:8080/fhir
and to set up the provider in the pseudonym service with the following steps:
1. do the following curl request in the terminal
```curl
curl -X 'POST' \
'http://localhost:8504/register' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"provider_id": "MY_HAPI",
"bsn_hash": "2b826afa1c71f571a007dad61ce9fb9b63a24a328aa4ac009484a198ff565c51"
}'
```
2. Copy generated pseudonym, it would look like the following:
```{"pseudonym":"79693551-2195-464f-9e8f-b5a7b90ec854"}```
3. Open database editor to the `hapi` database
4. Go to table `hfj_res_ver`
5. Edit the extension in the `patient` resource entry
![assets/img.png](assets/img.png)
6. Replace the patient's extensions UUID with the copied pseudonym

Finally test whether the pseudonym service and the right pseudonym are all setup correctly:
1. Do curl request, this same pseudonym is used by the timeline service as a sample pseudonym and it is coupled to the same bsn_hash that was used with setting the provider ID:
```curl
curl 'http://localhost:8080/fhir/ImagingStudy/_search?pseudonym=677b33c7-30e0-4fe1-a740-87fd73c4dfaf'
```
If you do not get any imaging studies back in the response then the HAPI is not set up correctly.
7. If you do get a correct result back then try if [timeline service](http://localhost:8500/) works as well
8. Use for the BSN the input: `123456789` This will load the created patient

2. If you do get imaging studies back then try if [timeline service](http://localhost:8500/) works as well
### Review
* Addresses database received a new endpoint, pointing to this HAPI.
* The pseudonyms database contains three extra pseudonyms from three providers, Timeline service, HAPI, referral service.
* In the referrals database, a row is added with a pseudonym, URA that corresponds with the addresses DB and with data domain set to beeldbank.

### Setting properties
In the [application.yaml](src/main/resources/application.yaml) file, you can configure most server and HAPI settings, enable or disable components, and set custom resource providers and interceptors.
Expand Down
73 changes: 73 additions & 0 deletions add_imagingStudy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/bin/bash

curl -X 'POST' \
'http://host.docker.internal:8080/fhir/ImagingStudy' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "ImagingStudy",
"status": "available",
"subject": {
"reference": "Patient/3"
},
"started": "2011-01-01T11:01:20+03:00",
"series": [
{
"uid": "series1",
"started": "2011-01-01T11:01:20+03:00",
"modality": {
"coding": [
{
"system": "https://dicom.nema.org/resources/ontology/DCM",
"code": "CT"
}
]
},
"performer": [
{
"actor": {
"reference": "Organization/1",
"type": "Organization"
}
},
{
"actor": {
"reference": "Practitioner/2",
"type": "Practitioner"
}
}
],
"bodySite": {
"concept": {
"coding": [
{
"system": "https://snomed.info/sct",
"code": "51185008",
"display": "CT of Head"
}
]
}
},
"instance": [
{
"uid": "instance201",
"sopClass": {
"system": "https://dicom.nema.org/resources/ontology/DCM",
"code": "CT01",
"display": "CT Head"
},
"title": "CT Head Image 1"
},
{
"uid": "instance202",
"sopClass": {
"system": "https://dicom.nema.org/resources/ontology/DCM",
"code": "CT02",
"display": "CT Head"
},
"title": "CT Head Image 2"
}
]
}
]
}' > /dev/null
18 changes: 18 additions & 0 deletions create_patient.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

curl -X 'POST' \
'http://localhost:8502/metadata_endpoint/add-one' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"ura_number": 12345678,
"data_domain": "beeldbank",
"endpoint": "http://host.docker.internal:8080/fhir",
"request_type": "GET",
"parameters": []
}' > /dev/null


curl -X POST http://host.docker.internal:8080/fhir \
-H "Content-Type: application/json" \
--data @fhir_examples/bundle.json > /dev/null
19 changes: 19 additions & 0 deletions src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class AppProperties {
private Subscription subscription = new Subscription();
private Cors cors = null;
private PseudonymExchangeService pseudonymExchangeService = null;
private AddReferralService addReferralService = null;
private Partitioning partitioning = null;
private Boolean install_transitive_ig_dependencies = true;
private Map<String, PackageInstallationSpec> implementationGuides = null;
Expand Down Expand Up @@ -212,6 +213,13 @@ public void setPseudonymExchangeService(PseudonymExchangeService pseudonymExchan
this.pseudonymExchangeService = pseudonymExchangeService;
}

public AddReferralService addReferralService() {
return addReferralService;
}
public void setAddReferralService(AddReferralService addReferralService) {
this.addReferralService = addReferralService;
}

public List<Bundle.BundleType> getAllowed_bundle_types() {
return allowed_bundle_types;
}
Expand Down Expand Up @@ -670,7 +678,18 @@ public String getTargetProviderId() {
public void setTargetProviderId(String targetProviderId) {
this.targetProviderId = targetProviderId;
}
}

public static class AddReferralService {
private String endpoint = "";

public String getEndpoint() {
return endpoint;
}

public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
}

public static class Cors {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.starter.ResourceProvider.BundlePlainProvider;
import ca.uhn.fhir.jpa.starter.services.CommonServices;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.hl7.fhir.r5.model.*;
Expand All @@ -13,9 +14,10 @@ public class EnablePlainProvider extends RestfulServer {
public EnablePlainProvider(IFhirResourceDao<Patient> patientDao,
IFhirResourceDao<Organization> orgDao,
IFhirResourceDao<Practitioner> pracDao,
IFhirResourceDao<ImagingStudy> imageDao
IFhirResourceDao<ImagingStudy> imageDao,
CommonServices commonServices
) {
registerProvider(new BundlePlainProvider(patientDao, orgDao, pracDao, imageDao));
registerProvider(new BundlePlainProvider(patientDao, orgDao, pracDao, imageDao, commonServices));

List<IResourceProvider> resourceProviders = new ArrayList<>();
registerProviders(resourceProviders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import ca.uhn.fhir.interceptor.api.Pointcut;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Patient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.List;

/**
* This class intercepts outgoing responses and removes patient extensions since they contain pseudonyms and
Expand All @@ -17,23 +20,42 @@
@Interceptor
public class RemovePatientExtensionsInterceptor {

private static final Logger log = LoggerFactory.getLogger(RemovePatientExtensionsInterceptor.class);

/**
* This function hooks into the server outgoing responses and removes
* any pseudonym extensions that patients might have.
*
* @param resource
* The resource of the outgoing response
*/
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public void removeExtensions(IBaseResource resource) {
public void checkForPatient(IBaseResource resource) {
log.info("Removing patient pseudonym extensions");
if (resource instanceof Patient patient) {
if (patient.hasExtension()) {
patient.setExtension(Collections.emptyList());
}
removeExtension(patient);
}
if (resource instanceof Bundle bundle) {
thomas-samoht marked this conversation as resolved.
Show resolved Hide resolved
if (bundle.hasEntry()) {
for (Bundle.BundleEntryComponent entry: bundle.getEntry()){
if (entry.getResource() instanceof Patient patient) {
if (patient.hasExtension()) {
patient.setExtension(Collections.emptyList());
}
removeExtension(patient);
}
}
}
}
}

/**
* @param patient
* The patient that could have a pseudonym extension
*/
private void removeExtension(Patient patient) {
// Retrieve the list of current extensions, excluding the one with the specific URL
List<Extension> updatedExtensions = patient.getExtension().stream()
.filter(ext -> !ext.getUrl().equals("https://example.com/extensions#pseudonym"))
.toList();
patient.setExtension(updatedExtensions);
log.info("Patient pseudonym extensions removed");
}
}
Loading