Skip to content

Commit 4006f3b

Browse files
committed
refactor: migrate dhis2-to-fhir-bundle example to use Camel DHIS2 component
1 parent 953fe6b commit 4006f3b

25 files changed

+400
-365
lines changed

dhis2-to-fhir-bundle/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
About
22
=====
33

4-
This Apache Camel application shows how DHIS2 resources can be turned into their FHIR counterparts and pushed as bundles to a FHIR server. Concretely, [organisation units](https://docs.dhis2.org/en/implement/database-design/organisation-units.html) are transformed into [organizations](https://hl7.org/fhir/R4/organization.html), [tracked entities](https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-240/tracker.html?h=tracked+entity+2.40#tracked-entity) into [patients](https://hl7.org/fhir/R4/patient.html), and [option sets](https://docs.dhis2.org/en/use/user-guides/dhis-core-version-240/configuring-the-system/metadata.html?h=option+sets+2.40#about_option_set) into [code systems](https://hl7.org/fhir/R4/codesystem.html) as well as [value sets](https://hl7.org/fhir/R4/valueset.html).
4+
This Apache Camel application shows how DHIS2 resources can be turned into their FHIR counterparts and pushed as bundles to a FHIR server. Concretely, the [Camel DHIS2 component](https://camel.apache.org/components/4.0.x/dhis2-component.html) is used to fetch [organisation units](https://docs.dhis2.org/en/implement/database-design/organisation-units.html), [tracked entities](https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-240/tracker.html?h=tracked+entity+2.40#tracked-entity), and [option sets](https://docs.dhis2.org/en/use/user-guides/dhis-core-version-240/configuring-the-system/metadata.html?h=option+sets+2.40#about_option_set) from DHIS2. Thanks to [Camel's type converters](https://camel.apache.org/manual/type-converter.html), the fetched resources are transformed as follows:
5+
6+
* Organisation units into [organizations](https://hl7.org/fhir/R4/organization.html) and [locations](https://hl7.org/fhir/R4/location.html)
7+
* Tracked entities into [patients](https://hl7.org/fhir/R4/patient.html)
8+
* Option sets into [code systems](https://hl7.org/fhir/R4/codesystem.html) and [value sets](https://hl7.org/fhir/R4/valueset.html)
9+
510

611
### How to run
712

dhis2-to-fhir-bundle/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
<groupId>org.apache.camel.springboot</groupId>
6262
<artifactId>camel-jackson-starter</artifactId>
6363
</dependency>
64+
<dependency>
65+
<groupId>org.hisp.dhis.integration.camel</groupId>
66+
<artifactId>camel-dhis2</artifactId>
67+
<version>3.0.0</version>
68+
</dependency>
6469
<dependency>
6570
<groupId>org.springframework.boot</groupId>
6671
<artifactId>spring-boot-devtools</artifactId>

dhis2-to-fhir-bundle/src/main/java/org/hisp/dhis/fhir/MainApplication.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import lombok.RequiredArgsConstructor;
66
import org.hisp.dhis.fhir.config.properties.DhisProperties;
77
import org.hisp.dhis.fhir.config.properties.FhirProperties;
8+
import org.hisp.dhis.integration.sdk.Dhis2ClientBuilder;
9+
import org.hisp.dhis.integration.sdk.api.Dhis2Client;
10+
import org.springframework.beans.factory.annotation.Autowired;
811
import org.springframework.boot.SpringApplication;
912
import org.springframework.boot.autoconfigure.SpringBootApplication;
1013
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -17,6 +20,9 @@ public class MainApplication
1720
{
1821
private final FhirProperties fhirProperties;
1922

23+
@Autowired
24+
private DhisProperties dhisProperties;
25+
2026
@Bean
2127
public FhirContext fhirContext()
2228
{
@@ -27,7 +33,14 @@ public FhirContext fhirContext()
2733
public IGenericClient fhirClient( FhirContext fhirContext )
2834
{
2935
fhirContext.getRestfulClientFactory().setSocketTimeout( 50000 );
30-
return fhirContext.newRestfulGenericClient( fhirProperties.getServerUrl() ) ;
36+
return fhirContext.newRestfulGenericClient( fhirProperties.getServerUrl() );
37+
}
38+
39+
@Bean
40+
public Dhis2Client dhis2Client()
41+
{
42+
return Dhis2ClientBuilder.newClient( dhisProperties.getBaseUrl(), dhisProperties.getUsername(),
43+
dhisProperties.getPassword() ).build();
3144
}
3245

3346
public static void main( String[] args )
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.hisp.dhis.fhir.aggregate;
2+
3+
import org.apache.camel.AggregationStrategy;
4+
import org.apache.camel.Exchange;
5+
import org.apache.camel.ExchangePropertyKey;
6+
import org.apache.camel.component.fhir.internal.FhirConstants;
7+
import org.hl7.fhir.r4.model.Bundle;
8+
9+
import java.util.List;
10+
11+
public abstract class AbstractBundleAggregationStrategy implements AggregationStrategy
12+
{
13+
public boolean isStoreAsBodyOnCompletion()
14+
{
15+
return true;
16+
}
17+
18+
@Override
19+
public void onCompletion( Exchange exchange )
20+
{
21+
if ( exchange != null && isStoreAsBodyOnCompletion() )
22+
{
23+
Bundle bundle = (Bundle) exchange.removeProperty( ExchangePropertyKey.GROUPED_EXCHANGE );
24+
if ( bundle != null )
25+
{
26+
exchange.getIn().setBody( bundle );
27+
exchange.getIn().setHeader( FhirConstants.PROPERTY_PREFIX + "bundle", bundle );
28+
}
29+
}
30+
}
31+
32+
@Override
33+
public Exchange aggregate( Exchange oldExchange, Exchange newExchange )
34+
{
35+
Bundle bundle;
36+
37+
if ( oldExchange == null )
38+
{
39+
bundle = getBundle( newExchange );
40+
}
41+
else
42+
{
43+
bundle = getBundle( oldExchange );
44+
}
45+
46+
if ( newExchange != null )
47+
{
48+
List<Bundle.BundleEntryComponent> entries = getEntries( newExchange );
49+
if ( entries != null )
50+
{
51+
for ( Bundle.BundleEntryComponent entry : entries )
52+
{
53+
bundle.addEntry( entry );
54+
}
55+
}
56+
}
57+
58+
return oldExchange != null ? oldExchange : newExchange;
59+
}
60+
61+
protected abstract List<Bundle.BundleEntryComponent> getEntries( Exchange exchange );
62+
63+
protected abstract Bundle createBundle();
64+
65+
private Bundle getBundle( Exchange exchange )
66+
{
67+
Bundle bundle = exchange.getProperty( ExchangePropertyKey.GROUPED_EXCHANGE, Bundle.class );
68+
if ( bundle == null )
69+
{
70+
bundle = createBundle();
71+
exchange.setProperty( ExchangePropertyKey.GROUPED_EXCHANGE, bundle );
72+
}
73+
return bundle;
74+
}
75+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.hisp.dhis.fhir.aggregate;
2+
3+
import org.apache.camel.Exchange;
4+
import org.hisp.dhis.fhir.domain.CodeSystemAndValueSet;
5+
import org.hl7.fhir.r4.model.Bundle;
6+
7+
import java.util.List;
8+
9+
public class CodeSystemAndValueSetBundleAggregationStrategy extends AbstractBundleAggregationStrategy
10+
{
11+
@Override
12+
protected List<Bundle.BundleEntryComponent> getEntries( Exchange exchange )
13+
{
14+
CodeSystemAndValueSet codeSystemAndValueSet = exchange.getIn().getBody( CodeSystemAndValueSet.class );
15+
16+
Bundle.BundleEntryComponent codeSystemEntry = new Bundle.BundleEntryComponent();
17+
Bundle.BundleEntryComponent valueSetEntry = new Bundle.BundleEntryComponent();
18+
19+
codeSystemEntry.setResource( codeSystemAndValueSet.getCodeSystem() )
20+
.getRequest().setUrl( "CodeSystem?identifier=" + codeSystemAndValueSet.getCodeSystem().getId() )
21+
.setMethod( Bundle.HTTPVerb.PUT );
22+
23+
valueSetEntry.setResource( codeSystemAndValueSet.getValueSet() )
24+
.getRequest().setUrl( "ValueSet?identifier=" + codeSystemAndValueSet.getValueSet().getId() )
25+
.setMethod( Bundle.HTTPVerb.PUT );
26+
27+
return List.of( codeSystemEntry, valueSetEntry );
28+
}
29+
30+
@Override
31+
protected Bundle createBundle()
32+
{
33+
return new Bundle().setType( Bundle.BundleType.TRANSACTION );
34+
}
35+
36+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.hisp.dhis.fhir.aggregate;
2+
3+
import org.apache.camel.Exchange;
4+
import org.hisp.dhis.fhir.domain.OrganizationAndLocation;
5+
import org.hl7.fhir.r4.model.Bundle;
6+
7+
import java.util.List;
8+
9+
public class OrganizationAndLocationBundleAggregationStrategy extends AbstractBundleAggregationStrategy
10+
{
11+
@Override
12+
protected List<Bundle.BundleEntryComponent> getEntries( Exchange exchange )
13+
{
14+
OrganizationAndLocation organizationAndLocation = exchange.getIn().getBody( OrganizationAndLocation.class );
15+
16+
Bundle.BundleEntryComponent organizationEntry = new Bundle.BundleEntryComponent();
17+
Bundle.BundleEntryComponent locationEntry = new Bundle.BundleEntryComponent();
18+
19+
organizationEntry.setResource( organizationAndLocation.getOrganization() )
20+
.getRequest().setUrl( "Organization?identifier=" + organizationAndLocation.getOrganization().getId() )
21+
.setMethod( Bundle.HTTPVerb.POST )
22+
.setIfNoneExist( "identifier=" + organizationAndLocation.getOrganization().getId() );
23+
24+
locationEntry.setResource( organizationAndLocation.getLocation() )
25+
.getRequest().setUrl( "Location?identifier=" + organizationAndLocation.getLocation().getId() )
26+
.setMethod( Bundle.HTTPVerb.POST )
27+
.setIfNoneExist( "identifier=" + organizationAndLocation.getLocation().getId() );
28+
29+
return List.of( organizationEntry, locationEntry );
30+
}
31+
32+
@Override
33+
protected Bundle createBundle()
34+
{
35+
return new Bundle().setType( Bundle.BundleType.TRANSACTION );
36+
}
37+
38+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.hisp.dhis.fhir.aggregate;
2+
3+
import org.apache.camel.Exchange;
4+
import org.hl7.fhir.r4.model.Bundle;
5+
import org.hl7.fhir.r4.model.Patient;
6+
7+
import java.util.List;
8+
9+
public class PatientBundleAggregationStrategy extends AbstractBundleAggregationStrategy
10+
{
11+
@Override
12+
protected List<Bundle.BundleEntryComponent> getEntries( Exchange exchange )
13+
{
14+
Patient patient = exchange.getIn().getBody( Patient.class );
15+
Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent();
16+
entry.setResource( patient ).getRequest().setUrl( "Patient?identifier=" + patient.getId() )
17+
.setMethod( Bundle.HTTPVerb.PUT );
18+
19+
return List.of( entry );
20+
}
21+
22+
@Override
23+
protected Bundle createBundle()
24+
{
25+
return new Bundle().setType( Bundle.BundleType.BATCH );
26+
}
27+
28+
}

dhis2-to-fhir-bundle/src/main/java/org/hisp/dhis/fhir/converters/OptionSetToFhirBundleConverter.java renamed to dhis2-to-fhir-bundle/src/main/java/org/hisp/dhis/fhir/converters/OptionSetToCodeSystemAndValueSetConverter.java

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
import org.apache.camel.Converter;
55
import org.apache.camel.Exchange;
66
import org.apache.camel.TypeConverters;
7-
import org.apache.camel.component.fhir.internal.FhirConstants;
7+
import org.hisp.dhis.api.model.v2_39_1.Option;
8+
import org.hisp.dhis.api.model.v2_39_1.OptionSet;
89
import org.hisp.dhis.fhir.config.properties.DhisProperties;
9-
import org.hisp.dhis.fhir.domain.Option;
10-
import org.hisp.dhis.fhir.domain.OptionSet;
11-
import org.hisp.dhis.fhir.domain.OptionSets;
12-
import org.hl7.fhir.r4.model.Bundle;
10+
import org.hisp.dhis.fhir.domain.CodeSystemAndValueSet;
1311
import org.hl7.fhir.r4.model.CodeSystem;
1412
import org.hl7.fhir.r4.model.Enumerations;
1513
import org.hl7.fhir.r4.model.Identifier;
@@ -20,43 +18,30 @@
2018

2119
@Component
2220
@RequiredArgsConstructor
23-
public class OptionSetToFhirBundleConverter implements TypeConverters
21+
public class OptionSetToCodeSystemAndValueSetConverter implements TypeConverters
2422
{
2523
private final DhisProperties dhisProperties;
2624

2725
@Converter
28-
public Bundle osToFhirBundle( OptionSets optionSets, Exchange exchange )
26+
public CodeSystemAndValueSet optionSetToCodeSystemAndValueSet( OptionSet optionSet, Exchange exchange )
2927
{
30-
Bundle bundle = new Bundle().setType( Bundle.BundleType.TRANSACTION );
28+
CodeSystem codeSystem = createCodeSystem( optionSet );
29+
ValueSet valueSet = createValueSet( optionSet );
3130

32-
for ( OptionSet optionSet : optionSets.getOptionSets() )
33-
{
34-
CodeSystem codeSystem = createCodeSystem( optionSet );
35-
ValueSet valueSet = createValueSet( optionSet );
36-
37-
bundle.addEntry().setResource( codeSystem )
38-
.getRequest().setUrl( "CodeSystem?identifier=" + codeSystem.getId() ).setMethod( Bundle.HTTPVerb.PUT );
39-
40-
bundle.addEntry().setResource( valueSet )
41-
.getRequest().setUrl( "ValueSet?identifier=" + valueSet.getId() ).setMethod( Bundle.HTTPVerb.PUT );
42-
}
43-
44-
exchange.getIn().setHeader( FhirConstants.PROPERTY_PREFIX + "bundle", bundle );
45-
46-
return bundle;
31+
return new CodeSystemAndValueSet( codeSystem, valueSet );
4732
}
4833

4934
private CodeSystem createCodeSystem( OptionSet optionSet )
5035
{
5136
String namespace = dhisProperties.getBaseUrl() + "/api/optionSets";
5237

5338
CodeSystem codeSystem = new CodeSystem();
54-
codeSystem.setId( optionSet.getId() );
39+
codeSystem.setId( optionSet.getId().get() );
5540
codeSystem.setUrl( namespace + "/" + optionSet.getId() + "/codeSystem" );
5641
codeSystem.setValueSet( namespace + "/" + optionSet.getId() + "/valueSet" );
57-
codeSystem.setName( optionSet.getName() );
58-
codeSystem.setTitle( optionSet.getName() );
59-
codeSystem.setDescription( optionSet.getDescription() );
42+
codeSystem.setName( optionSet.getName().get() );
43+
codeSystem.setTitle( optionSet.getName().get() );
44+
codeSystem.setDescription( (String) optionSet.getAdditionalProperties().get( "description" ) );
6045
codeSystem.setPublisher( dhisProperties.getBaseUrl() );
6146
codeSystem.setStatus( Enumerations.PublicationStatus.ACTIVE );
6247
codeSystem.setContent( CodeSystem.CodeSystemContentMode.COMPLETE );
@@ -65,22 +50,22 @@ private CodeSystem createCodeSystem( OptionSet optionSet )
6550
codeSystem.setCaseSensitive( true );
6651

6752
codeSystem.getIdentifier().add(
68-
new Identifier().setSystem( namespace ).setValue( optionSet.getId() )
53+
new Identifier().setSystem( namespace ).setValue( optionSet.getId().get() )
6954
);
7055

71-
if ( hasText( optionSet.getCode() ) )
56+
if ( hasText( optionSet.getCode().orElse( null ) ) )
7257
{
7358
codeSystem.getIdentifier().add(
74-
new Identifier().setSystem( namespace ).setValue( optionSet.getCode() )
59+
new Identifier().setSystem( namespace ).setValue( optionSet.getCode().get() )
7560
);
7661
}
7762

78-
for ( Option option : optionSet.getOptions() )
63+
for ( Option option : optionSet.getOptions().get() )
7964
{
8065
codeSystem.addConcept()
81-
.setDisplay( option.getName() )
82-
.setDefinition( option.getName() )
83-
.setCode( option.getCode() );
66+
.setDisplay( option.getName().get() )
67+
.setDefinition( option.getName().get() )
68+
.setCode( option.getCode().orElse( null ) );
8469
}
8570

8671
return codeSystem;
@@ -91,29 +76,30 @@ private ValueSet createValueSet( OptionSet optionSet )
9176
String namespace = dhisProperties.getBaseUrl() + "/api/optionSets";
9277

9378
ValueSet valueSet = new ValueSet();
94-
valueSet.setId( optionSet.getId() );
79+
valueSet.setId( optionSet.getId().get() );
9580
valueSet.setUrl( namespace + "/" + optionSet.getId() + "/valueSet" );
96-
valueSet.setName( optionSet.getName() );
97-
valueSet.setTitle( optionSet.getName() );
98-
valueSet.setDescription( optionSet.getDescription() );
81+
valueSet.setName( optionSet.getName().get() );
82+
valueSet.setTitle( optionSet.getName().get() );
83+
valueSet.setDescription( (String) optionSet.getAdditionalProperties().get( "description" ) );
9984
valueSet.setStatus( Enumerations.PublicationStatus.ACTIVE );
10085
valueSet.setVersion( String.valueOf( optionSet.getVersion() ) );
10186
valueSet.setExperimental( false );
10287
valueSet.setImmutable( true );
10388

10489
valueSet.getIdentifier().add(
105-
new Identifier().setSystem( namespace ).setValue( optionSet.getId() )
90+
new Identifier().setSystem( namespace ).setValue( optionSet.getId().get() )
10691
);
10792

108-
if ( hasText( optionSet.getCode() ) )
93+
if ( hasText( optionSet.getCode().orElse( null ) ) )
10994
{
11095
valueSet.getIdentifier().add(
111-
new Identifier().setSystem( namespace ).setValue( optionSet.getCode() )
96+
new Identifier().setSystem( namespace ).setValue( optionSet.getCode().get() )
11297
);
11398
}
11499

115100
valueSet.setCompose( new ValueSet.ValueSetComposeComponent()
116-
.addInclude( new ValueSet.ConceptSetComponent().setSystem( namespace + "/" + optionSet.getId() + "/codeSystem" ) ) );
101+
.addInclude(
102+
new ValueSet.ConceptSetComponent().setSystem( namespace + "/" + optionSet.getId() + "/codeSystem" ) ) );
117103

118104
return valueSet;
119105
}

0 commit comments

Comments
 (0)