So, you've built your HAPI FHIR Server, and if you started when I did, you may have even started with STU2. But these days IHE profiles are in STU3. What's a guy to do? Well, if that guy is an engineer, the first thing you WON'T DO is rewrite your server from scratch. For PIXm, PDQm, QEDm and likely even MHD (although I haven't checked that profile out yet), the simple answer is to front end your STU2 services with an STU3 facade.
The pattern for such a facade is fairly simple:
public class MySTU3ResourceProvider {
private final MySTU2ResourceProvider stu2Provider;
private VersionConvertor_10_30 converter =
new VersionConvertor_10_30(new NullVersionConverterAdvisor30());
public MySTU3ResourceProvider(MySTU2ResourceProvider stu2Provider) {
this.stu2Provider = stu2Provider;
}
@Search() Bundle doQuery(...) {
try {
// call the previous method and get the bundle response
return coverter.convertResource(stu2Provider.doQuery(...));
} catch (BaseServerResponseException ex) {
// We got a "normal" exception from the processing.
Object o =
converter.convertOperationOutcome(
(OperationOutcome) ex.getOperationOutcome());
ex.setOperationOutcome((IBaseOperationOutcome) o);
throw ex;
} catch (FHIRException fex) {
throw new InternalErrorException(fex);
}
}
@Read Resource doRead() {
try {
// call the previous method and get the bundle response
return coverter.convertResource(stu2Provider.doRead(...));
} catch (BaseServerResponseException ex) {
Object o =
converter.convertOperationOutcome(
(OperationOutcome) ex.getOperationOutcome());
ex.setOperationOutcome((IBaseOperationOutcome) o);
throw ex;
} catch (FHIRException fex) {
throw new InternalErrorException(fex);
}
}
}
One of the things I learned this week was that I needed to add the conversion trick to handle any cases where the original method throws something derived from BaseServerResponseException.
This is great. Until you discover that for some reason, convertMedicationOrder hasn't been implemented, and you need to turn your previous MedicationOrder into a MedicationRequest.
So, buckle down and write convertMedicationRequest(), or use the code I wrote below. Here's how I started: It took me about 3 minutes to find that the code had been commented out. It took me another 15 or so to code it up, and a few more to test and tweak it. All told, about 30 minutes. To skip to the chase, here's the code (it's not pretty, but it seems to compile and run):
VersionConvertor_10_30 converter = new VersionConvertor_10_30(new NullVersionConverterAdvisor30()) {
public org.hl7.fhir.dstu3.model.Resource convertResource(org.hl7.fhir.instance.model.Resource src) throws FHIRException {
if (src instanceof org.hl7.fhir.instance.model.MedicationOrder) {
return convertMedicationOrder((org.hl7.fhir.instance.model.MedicationOrder)src);
}
return super.convertResource(src);
}
public org.hl7.fhir.dstu3.model.MedicationRequest convertMedicationOrder(org.hl7.fhir.instance.model.MedicationOrder src) throws FHIRException {
if (src == null || src.isEmpty())
return null;
org.hl7.fhir.dstu3.model.MedicationRequest tgt = new org.hl7.fhir.dstu3.model.MedicationRequest();
copyDomainResource(src, tgt);
for (org.hl7.fhir.instance.model.Identifier t : src.getIdentifier())
tgt.addIdentifier(convertIdentifier(t));
tgt.setStatus(MedicationRequestStatus.fromCode(src.getStatus().toCode()));
tgt.setMedication(convertType(src.getMedication()));
tgt.setSubject(convertReference(src.getPatient()));
tgt.setContext(convertReference(src.getEncounter()));
if (src.hasDateWritten())
tgt.setAuthoredOn(src.getDateWritten());
tgt.getRequester().setAgent(convertReference(src.getPrescriber()));
if (src.hasReasonCodeableConcept())
tgt.addReasonCode(convertCodeableConcept(src.getReasonCodeableConcept()));
if (src.hasReasonReference())
tgt.addReasonReference(convertReference(src.getReasonReference()));
if (src.hasNote())
tgt.addNote().setText(src.getNote());
for (org.hl7.fhir.instance.model.MedicationOrder.MedicationOrderDosageInstructionComponent t : src.getDosageInstruction())
tgt.addDosageInstruction(convertMedicationOrderDosageInstructionComponent(t));
if (src.hasDispenseRequest()) {
MedicationRequestDispenseRequestComponent dr = tgt.getDispenseRequest();
MedicationOrderDispenseRequestComponent sr = src.getDispenseRequest();
if (sr.hasValidityPeriod())
dr.setValidityPeriod(convertPeriod(sr.getValidityPeriod()));
if (sr.hasNumberOfRepeatsAllowed())
dr.setNumberOfRepeatsAllowed(sr.getNumberOfRepeatsAllowed());
if (sr.hasQuantity())
dr.setQuantity(convertSimpleQuantity(sr.getQuantity()));
if (sr.hasExpectedSupplyDuration())
dr.setExpectedSupplyDuration(convertDuration(sr.getExpectedSupplyDuration()));
}
if (src.hasSubstitution())
tgt.getSubstitution().setReason(convertCodeableConcept(src.getSubstitution().getReason()));
tgt.setPriorPrescription(convertReference(src.getPriorPrescription()));
return tgt;
}
};
This week is all about getting things close enough. Afterwards I get to bring the code back to turn it into production. But I'm guessing that I'm not the only one wanting to convert STU2 MedicationOrder resources into STU3 MedicationRequest resources. So, here you go.
Keith
P.S. As to why this is commented out, my suspicion is that the code generator that is producing the converter from the FHIR Specification hasn't quite been tuned to deal with Resource name changes yet.
P.P.S. Source code in this post is CC-By 3.0
The pattern for such a facade is fairly simple:
public class MySTU3ResourceProvider {
private final MySTU2ResourceProvider stu2Provider;
private VersionConvertor_10_30 converter =
new VersionConvertor_10_30(new NullVersionConverterAdvisor30());
public MySTU3ResourceProvider(MySTU2ResourceProvider stu2Provider) {
this.stu2Provider = stu2Provider;
}
@Search() Bundle doQuery(...) {
try {
// call the previous method and get the bundle response
return coverter.convertResource(stu2Provider.doQuery(...));
} catch (BaseServerResponseException ex) {
// We got a "normal" exception from the processing.
Object o =
converter.convertOperationOutcome(
(OperationOutcome) ex.getOperationOutcome());
ex.setOperationOutcome((IBaseOperationOutcome) o);
throw ex;
} catch (FHIRException fex) {
throw new InternalErrorException(fex);
}
}
@Read Resource doRead() {
try {
// call the previous method and get the bundle response
return coverter.convertResource(stu2Provider.doRead(...));
} catch (BaseServerResponseException ex) {
Object o =
converter.convertOperationOutcome(
(OperationOutcome) ex.getOperationOutcome());
ex.setOperationOutcome((IBaseOperationOutcome) o);
throw ex;
} catch (FHIRException fex) {
throw new InternalErrorException(fex);
}
}
}
One of the things I learned this week was that I needed to add the conversion trick to handle any cases where the original method throws something derived from BaseServerResponseException.
This is great. Until you discover that for some reason, convertMedicationOrder hasn't been implemented, and you need to turn your previous MedicationOrder into a MedicationRequest.
So, buckle down and write convertMedicationRequest(), or use the code I wrote below. Here's how I started: It took me about 3 minutes to find that the code had been commented out. It took me another 15 or so to code it up, and a few more to test and tweak it. All told, about 30 minutes. To skip to the chase, here's the code (it's not pretty, but it seems to compile and run):
VersionConvertor_10_30 converter = new VersionConvertor_10_30(new NullVersionConverterAdvisor30()) {
public org.hl7.fhir.dstu3.model.Resource convertResource(org.hl7.fhir.instance.model.Resource src) throws FHIRException {
if (src instanceof org.hl7.fhir.instance.model.MedicationOrder) {
return convertMedicationOrder((org.hl7.fhir.instance.model.MedicationOrder)src);
}
return super.convertResource(src);
}
public org.hl7.fhir.dstu3.model.MedicationRequest convertMedicationOrder(org.hl7.fhir.instance.model.MedicationOrder src) throws FHIRException {
if (src == null || src.isEmpty())
return null;
org.hl7.fhir.dstu3.model.MedicationRequest tgt = new org.hl7.fhir.dstu3.model.MedicationRequest();
copyDomainResource(src, tgt);
for (org.hl7.fhir.instance.model.Identifier t : src.getIdentifier())
tgt.addIdentifier(convertIdentifier(t));
tgt.setStatus(MedicationRequestStatus.fromCode(src.getStatus().toCode()));
tgt.setMedication(convertType(src.getMedication()));
tgt.setSubject(convertReference(src.getPatient()));
tgt.setContext(convertReference(src.getEncounter()));
if (src.hasDateWritten())
tgt.setAuthoredOn(src.getDateWritten());
tgt.getRequester().setAgent(convertReference(src.getPrescriber()));
if (src.hasReasonCodeableConcept())
tgt.addReasonCode(convertCodeableConcept(src.getReasonCodeableConcept()));
if (src.hasReasonReference())
tgt.addReasonReference(convertReference(src.getReasonReference()));
if (src.hasNote())
tgt.addNote().setText(src.getNote());
for (org.hl7.fhir.instance.model.MedicationOrder.MedicationOrderDosageInstructionComponent t : src.getDosageInstruction())
tgt.addDosageInstruction(convertMedicationOrderDosageInstructionComponent(t));
if (src.hasDispenseRequest()) {
MedicationRequestDispenseRequestComponent dr = tgt.getDispenseRequest();
MedicationOrderDispenseRequestComponent sr = src.getDispenseRequest();
if (sr.hasValidityPeriod())
dr.setValidityPeriod(convertPeriod(sr.getValidityPeriod()));
if (sr.hasNumberOfRepeatsAllowed())
dr.setNumberOfRepeatsAllowed(sr.getNumberOfRepeatsAllowed());
if (sr.hasQuantity())
dr.setQuantity(convertSimpleQuantity(sr.getQuantity()));
if (sr.hasExpectedSupplyDuration())
dr.setExpectedSupplyDuration(convertDuration(sr.getExpectedSupplyDuration()));
}
if (src.hasSubstitution())
tgt.getSubstitution().setReason(convertCodeableConcept(src.getSubstitution().getReason()));
tgt.setPriorPrescription(convertReference(src.getPriorPrescription()));
return tgt;
}
};
This week is all about getting things close enough. Afterwards I get to bring the code back to turn it into production. But I'm guessing that I'm not the only one wanting to convert STU2 MedicationOrder resources into STU3 MedicationRequest resources. So, here you go.
Keith
P.S. As to why this is commented out, my suspicion is that the code generator that is producing the converter from the FHIR Specification hasn't quite been tuned to deal with Resource name changes yet.
P.P.S. Source code in this post is CC-By 3.0
I maintain the Java conversion code manually. It's *hard* work. Sometimes I don't have hours to figure out how to fix something, so I comment it out. No one contributes to this code... it would be great if people did.
ReplyDeleteI discovered a missing element in ReferralRequest conversion from STU2 to STU3. The above guidance was extremely helpful in being able to resolve the issue.
ReplyDeleteorg.hl7.fhir.convertors.VersionConvertor_10_30.convertReferralRequest method is missing the setContext call to convert STU2 encounter to STU3 context.
tgt.setContext(convertReference(src.getEncounter()));
Can you submit a pull request here: https://github.com/hapifhir/org.hl7.fhir.core?
Delete