mirror of
https://github.com/hapifhir/org.hl7.fhir.core.git
synced 2025-03-09 14:31:17 +00:00
Merge branch 'master' of https://github.com/hapifhir/org.hl7.fhir.core
This commit is contained in:
commit
fc5a57c10b
@ -0,0 +1 @@
|
||||
* fix bug for NullPointerException in Bundle convertors when resource is not available.
|
@ -1,6 +1,15 @@
|
||||
# Resource Conversion
|
||||
|
||||
##### Let's talk about converting resources between the various versions of FHIR...
|
||||
## IMPORTANT
|
||||
-----
|
||||
|
||||
_The conversion code in this module is maintained as part of the development of the standard, but always under
|
||||
considerable time pressure. Only part of the code is rigorously tested [as detailed here](#reliable-conversion-code).
|
||||
Implementers should regard this code as a 'scaffold' for actual reliable conversions._
|
||||
|
||||
**ALWAYS TEST ANY CONVERSION ROUTINES BEFORE USING THEM IN PRODUCTION!**
|
||||
|
||||
_Ideally, this should be via unit tests in your code, or better yet [unit tests contributed to FHIR](#test-cases)._
|
||||
|
||||
### A note regarding syntax
|
||||
|
||||
@ -31,7 +40,7 @@ and [r4](http://hl7.org/fhir/R4/account.html)
|
||||
**N.B.** This information is only for code navigation purposes. It is important that when converting between versions
|
||||
you use the provided conversion factory classes as your entry point.
|
||||
|
||||
### Using the conversion library
|
||||
## Using the conversion library
|
||||
|
||||
-----
|
||||
The majority of use cases for conversion will involve using the provided VersionConvertorFactory_V1_V2 classes to convert
|
||||
@ -193,3 +202,31 @@ Once you've created your new advisor, they can be provided as an argument when c
|
||||
`public static (V1 Resource) convertResource((V2 Resource) src, <T extends BaseAdvisor> advisor)`
|
||||
|
||||
`public static (V2 Resource) convertResource((V1 Resource) src, <T extends BaseAdvisor> advisor)`
|
||||
|
||||
## Development notes
|
||||
-----
|
||||
|
||||
### Reliable conversion code
|
||||
|
||||
The FHIR project maintains and tests conversions on the following resources, from old versions to R5:
|
||||
|
||||
- CodeSystem
|
||||
- ValueSet
|
||||
- ConceptMap
|
||||
- StructureDefinition
|
||||
- StructureMap
|
||||
- ImplementationGuide
|
||||
- CapabilityStatement
|
||||
- OperationDefinition
|
||||
- NamingSystem
|
||||
|
||||
These can be relied on and are subject to extensive testing.
|
||||
|
||||
### Test cases
|
||||
|
||||
Some conversions have test cases for particular resources and particular version combinations. Where test cases exist,
|
||||
they will continue to be maintained and expected to pass.
|
||||
|
||||
Contributing test cases is highly encouraged! To contribute, create a PRs to the
|
||||
[core library](https://github.com/hapifhir/org.hl7.fhir.core), or even better, to the
|
||||
[FHIR test cases library](https://github.com/FHIR/fhir-test-cases).
|
@ -1,32 +0,0 @@
|
||||
About the version conversion routines
|
||||
|
||||
The version conversion routines are maintained as part of
|
||||
the development of the standard, but always under considerable
|
||||
time pressure. Implementers should regard these as 'scaffolds' for
|
||||
an actual reliable conversion routine.
|
||||
|
||||
The FHIR project maintains and tests conversions on the following
|
||||
resources, from old versions to R5:
|
||||
* CodeSystem
|
||||
* ValueSet
|
||||
* ConceptMap
|
||||
* StructureDefinition
|
||||
* StructureMap
|
||||
* ImplementationGuide
|
||||
* CapabilityStatement
|
||||
* OperationDefinition
|
||||
* NamingSystem
|
||||
|
||||
These can be relied on and are subject to extensive testing.
|
||||
|
||||
In addition to this, some of the conversions have test cases
|
||||
for particular resources and particular version combinations.
|
||||
Where test cases exist, they will continue to pass and be
|
||||
maintained.
|
||||
|
||||
So:
|
||||
* test the conversion routines before using them in production
|
||||
* contribute test cases to ensure that your use cases continue to be reliable
|
||||
|
||||
Test cases are welcome - make them as PRs to the core library, or even better,
|
||||
to the FHIR test cases library
|
@ -60,8 +60,8 @@ public class Bundle10_30 {
|
||||
tgt.addLink(convertBundleLinkComponent(t));
|
||||
if (src.hasFullUrlElement())
|
||||
tgt.setFullUrlElement(Uri10_30.convertUri(src.getFullUrlElement()));
|
||||
org.hl7.fhir.dstu2.model.Resource res = ConversionContext10_30.INSTANCE.getVersionConvertor_10_30().convertResource(src.getResource());
|
||||
tgt.setResource(res);
|
||||
if (src.hasResource())
|
||||
tgt.setResource(ConversionContext10_30.INSTANCE.getVersionConvertor_10_30().convertResource(src.getResource()));
|
||||
if (src.hasSearch())
|
||||
tgt.setSearch(convertBundleEntrySearchComponent(src.getSearch()));
|
||||
if (src.hasRequest())
|
||||
|
@ -78,8 +78,8 @@ public class Bundle10_40 {
|
||||
for (org.hl7.fhir.r4.model.Bundle.BundleLinkComponent t : src.getLink()) tgt.addLink(convertBundleLinkComponent(t));
|
||||
if (src.hasFullUrlElement())
|
||||
tgt.setFullUrlElement(Uri10_40.convertUri(src.getFullUrlElement()));
|
||||
org.hl7.fhir.dstu2.model.Resource res = ConversionContext10_40.INSTANCE.getVersionConvertor_10_40().convertResource(src.getResource());
|
||||
tgt.setResource(res);
|
||||
if (src.hasResource())
|
||||
tgt.setResource(ConversionContext10_40.INSTANCE.getVersionConvertor_10_40().convertResource(src.getResource()));
|
||||
if (src.hasSearch())
|
||||
tgt.setSearch(convertBundleEntrySearchComponent(src.getSearch()));
|
||||
if (src.hasRequest())
|
||||
|
@ -78,8 +78,8 @@ public class Bundle10_50 {
|
||||
for (org.hl7.fhir.r5.model.Bundle.BundleLinkComponent t : src.getLink()) tgt.addLink(convertBundleLinkComponent(t));
|
||||
if (src.hasFullUrlElement())
|
||||
tgt.setFullUrlElement(Uri10_50.convertUri(src.getFullUrlElement()));
|
||||
org.hl7.fhir.dstu2.model.Resource res = ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertResource(src.getResource());
|
||||
tgt.setResource(res);
|
||||
if (src.hasResource())
|
||||
tgt.setResource(ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertResource(src.getResource()));
|
||||
if (src.hasSearch())
|
||||
tgt.setSearch(convertBundleEntrySearchComponent(src.getSearch()));
|
||||
if (src.hasRequest())
|
||||
|
@ -0,0 +1,35 @@
|
||||
package org.hl7.fhir.convertors.conv10_30;
|
||||
|
||||
|
||||
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_30;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Bundle10_30Test {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 10_30 bundle conversion when resource is null")
|
||||
public void testNoResourceBundleConversion() throws IOException {
|
||||
org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent bec = new org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent()
|
||||
.setRequest(
|
||||
new org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent()
|
||||
.setMethod(org.hl7.fhir.dstu3.model.Bundle.HTTPVerb.DELETE)
|
||||
.setUrl("Patient?identifier=123456")
|
||||
);
|
||||
|
||||
org.hl7.fhir.dstu3.model.Bundle stu3Bundle = new org.hl7.fhir.dstu3.model.Bundle()
|
||||
.addEntry(bec);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Resource dstu2Resource = VersionConvertorFactory_10_30.convertResource(stu3Bundle);
|
||||
Assertions.assertNotNull(dstu2Resource);
|
||||
Assertions.assertTrue(dstu2Resource instanceof org.hl7.fhir.dstu2.model.Bundle);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Bundle dstu2Bundle = (org.hl7.fhir.dstu2.model.Bundle) dstu2Resource;
|
||||
Assertions.assertEquals(1, dstu2Bundle.getEntry().size());
|
||||
|
||||
Assertions.assertNull(dstu2Bundle.getEntry().get(0).getResource());
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.hl7.fhir.convertors.conv10_40;
|
||||
|
||||
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_40;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Bundle10_40Test {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 10_40 bundle conversion when resource is null")
|
||||
public void testNoResourceBundleConversion() throws IOException {
|
||||
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent bec = new org.hl7.fhir.r4.model.Bundle.BundleEntryComponent()
|
||||
.setRequest(
|
||||
new org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent()
|
||||
.setMethod(org.hl7.fhir.r4.model.Bundle.HTTPVerb.DELETE)
|
||||
.setUrl("Patient?identifier=123456")
|
||||
);
|
||||
|
||||
org.hl7.fhir.r4.model.Bundle r4Bundle = new org.hl7.fhir.r4.model.Bundle()
|
||||
.addEntry(bec);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Resource dstu2Resource = VersionConvertorFactory_10_40.convertResource(r4Bundle);
|
||||
Assertions.assertNotNull(dstu2Resource);
|
||||
Assertions.assertTrue(dstu2Resource instanceof org.hl7.fhir.dstu2.model.Bundle);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Bundle dstu2Bundle = (org.hl7.fhir.dstu2.model.Bundle) dstu2Resource;
|
||||
Assertions.assertEquals(1, dstu2Bundle.getEntry().size());
|
||||
|
||||
Assertions.assertNull(dstu2Bundle.getEntry().get(0).getResource());
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.hl7.fhir.convertors.conv10_50;
|
||||
|
||||
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Bundle10_50Test {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 10_50 bundle conversion when resource is null")
|
||||
public void testNoResourceBundleConversion() throws IOException {
|
||||
org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bec = new org.hl7.fhir.r5.model.Bundle.BundleEntryComponent()
|
||||
.setRequest(
|
||||
new org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent()
|
||||
.setMethod(org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE)
|
||||
.setUrl("Patient?identifier=123456")
|
||||
);
|
||||
|
||||
org.hl7.fhir.r5.model.Bundle r5Bundle = new org.hl7.fhir.r5.model.Bundle()
|
||||
.addEntry(bec);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Resource dstu2Resource = VersionConvertorFactory_10_50.convertResource(r5Bundle);
|
||||
Assertions.assertNotNull(dstu2Resource);
|
||||
Assertions.assertTrue(dstu2Resource instanceof org.hl7.fhir.dstu2.model.Bundle);
|
||||
|
||||
org.hl7.fhir.dstu2.model.Bundle dstu2Bundle = (org.hl7.fhir.dstu2.model.Bundle) dstu2Resource;
|
||||
Assertions.assertEquals(1, dstu2Bundle.getEntry().size());
|
||||
|
||||
Assertions.assertNull(dstu2Bundle.getEntry().get(0).getResource());
|
||||
}
|
||||
}
|
@ -1,33 +1,33 @@
|
||||
package org.hl7.fhir.r4.terminologies;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ public class ConceptMapEngine {
|
||||
for (ConceptMapGroupComponent g : cm.getGroup()) {
|
||||
for (SourceElementComponent e : g.getElement()) {
|
||||
if (code.equals(e.getCode())) {
|
||||
if (e != null)
|
||||
if (ct != null)
|
||||
throw new FHIRException("Unable to process translate "+code+" because multiple candidate matches were found in concept map "+cm.getUrl());
|
||||
ct = e;
|
||||
cg = g;
|
||||
@ -94,4 +94,4 @@ public class ConceptMapEngine {
|
||||
throw new Error("Not done yet");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
package org.hl7.fhir.r4.terminologies;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ConceptMapEngineTest {
|
||||
|
||||
private static final String CONCEPT_MAP_URL = "https://test-fhir.com/ConceptMap/fake";
|
||||
public static final String SOURCE_CODE_STRING = "body-weight";
|
||||
public static final String TARGET_CODE_STRING = "vital-signs";
|
||||
|
||||
@Test
|
||||
@DisplayName("Coding is translated according to ConceptMap")
|
||||
void codingTranslate() throws IOException {
|
||||
|
||||
final ConceptMap.SourceElementComponent sourceElementComponent = getSourceElementComponent();
|
||||
|
||||
final ConceptMapEngine conceptMapEngine = getConceptMapEngine(Arrays.asList(sourceElementComponent));
|
||||
Coding coding = new Coding(null, SOURCE_CODE_STRING, "Body Weight");
|
||||
|
||||
Coding actual = conceptMapEngine.translate(coding, CONCEPT_MAP_URL);
|
||||
|
||||
assertEquals(TARGET_CODE_STRING, actual.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Coding fails to translate due to multiple candidate matches in ConceptMap")
|
||||
void codingTranslateFailsForMultipleCandidateMatches() throws IOException {
|
||||
|
||||
final ConceptMap.SourceElementComponent sourceElementComponent = getSourceElementComponent();
|
||||
|
||||
final ConceptMapEngine conceptMapEngine = getConceptMapEngine(Arrays.asList(sourceElementComponent,
|
||||
sourceElementComponent
|
||||
));
|
||||
Coding coding = new Coding(null, SOURCE_CODE_STRING, "Body Weight");
|
||||
|
||||
assertThrows(FHIRException.class, () -> {
|
||||
conceptMapEngine.translate(coding, CONCEPT_MAP_URL);
|
||||
});
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ConceptMapEngine getConceptMapEngine(Collection<ConceptMap.SourceElementComponent> elements) throws IOException {
|
||||
ConceptMap conceptMap = getConceptMap(elements);
|
||||
|
||||
SimpleWorkerContext simpleWorkerContext = mock(SimpleWorkerContext.class);
|
||||
when(simpleWorkerContext.fetchResource(ConceptMap.class, CONCEPT_MAP_URL)).thenReturn(conceptMap);
|
||||
|
||||
return new ConceptMapEngine(simpleWorkerContext);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ConceptMap.SourceElementComponent getSourceElementComponent() {
|
||||
ConceptMap.TargetElementComponent targetElementComponent = new ConceptMap.TargetElementComponent();
|
||||
targetElementComponent.setCode(TARGET_CODE_STRING);
|
||||
targetElementComponent.setEquivalence(Enumerations.ConceptMapEquivalence.EQUIVALENT);
|
||||
|
||||
ConceptMap.SourceElementComponent sourceElementComponent = new ConceptMap.SourceElementComponent();
|
||||
sourceElementComponent.setCode(SOURCE_CODE_STRING);
|
||||
sourceElementComponent.setTarget(Collections.singletonList(targetElementComponent));
|
||||
|
||||
return sourceElementComponent;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ConceptMap getConceptMap(Collection<ConceptMap.SourceElementComponent> elements) {
|
||||
|
||||
ConceptMap.ConceptMapGroupComponent conceptMapGroupComponent = new ConceptMap.ConceptMapGroupComponent();
|
||||
for (ConceptMap.SourceElementComponent element : elements) {
|
||||
conceptMapGroupComponent.addElement(element);
|
||||
}
|
||||
return new ConceptMap()
|
||||
.addGroup(conceptMapGroupComponent)
|
||||
.setUrl(CONCEPT_MAP_URL);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user