Skip to content

Commit

Permalink
Issue #2512 - Bundle processing replace all local references (#3007)
Browse files Browse the repository at this point in the history
* Issue #2512 - Bundle processing replace all local references

Signed-off-by: Mike Schroeder <mschroed@us.ibm.com>

* Issue #2512 - address review comments

Signed-off-by: Mike Schroeder <mschroed@us.ibm.com>
  • Loading branch information
michaelwschroeder authored Dec 6, 2021
1 parent 7559066 commit 24f03c5
Show file tree
Hide file tree
Showing 10 changed files with 719 additions and 95 deletions.
141 changes: 89 additions & 52 deletions docs/src/pages/guides/FHIRServerUsersGuide.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2020
* (C) Copyright IBM Corp. 2020, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -178,7 +178,7 @@ protected void process(String originalName, Bundle bundle) {
if (HTTPVerb.PUT.equals(requestEntry.getRequest().getMethod())) {
// PUT remapped to PUT. The local id is already an external identifier
ReferenceMappingVisitor<Resource> visitor =
new ReferenceMappingVisitor<Resource>(localRefMap);
new ReferenceMappingVisitor<Resource>(localRefMap, localId);
resource.accept(visitor);
resource = visitor.getResult();

Expand All @@ -195,7 +195,7 @@ protected void process(String originalName, Bundle bundle) {
String externalId = localRefMap.get(localId);
if (externalId != null) {
String newId = externalId.substring(externalId.lastIndexOf('/')+1);
IdReferenceMappingVisitor visitor = new IdReferenceMappingVisitor(localRefMap, newId);
IdReferenceMappingVisitor visitor = new IdReferenceMappingVisitor(localRefMap, localId, newId);
resource.accept(visitor);
resource = visitor.getResult();

Expand All @@ -209,7 +209,7 @@ protected void process(String originalName, Bundle bundle) {
} else if (HTTPVerb.DELETE.equals(requestEntry.getRequest().getMethod())) {
// Update any local references contained within the resource
ReferenceMappingVisitor<Resource> visitor =
new ReferenceMappingVisitor<Resource>(localRefMap);
new ReferenceMappingVisitor<Resource>(localRefMap, localId);
resource.accept(visitor);
resource = visitor.getResult();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/*
* (C) Copyright IBM Corp. 2020
* (C) Copyright IBM Corp. 2020, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.bucket.scanner;

import static com.ibm.fhir.model.type.String.string;

import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -30,9 +28,11 @@ public class IdReferenceMappingVisitor extends ReferenceMappingVisitor<Resource>
/**
* Public constructor
* @param localRefMap
* @param localIdentifier
* @param newId
*/
public IdReferenceMappingVisitor(Map<String, String> localRefMap, String newId) {
super(localRefMap);
public IdReferenceMappingVisitor(Map<String, String> localRefMap, String localIdentifier, String newId) {
super(localRefMap, localIdentifier);
this.newId = newId;
}

Expand Down
40 changes: 39 additions & 1 deletion fhir-model/src/main/java/com/ibm/fhir/model/util/FHIRUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,46 @@ public static Bundle createStandaloneBundle(BundleType bundleType, Map<String,Re
.entry(entries)
.build();

ReferenceMappingVisitor<Bundle> referenceMappingVisitor = new ReferenceMappingVisitor<>(localRefMap);
ReferenceMappingVisitor<Bundle> referenceMappingVisitor = new ReferenceMappingVisitor<>(localRefMap, null);
bundle.accept(referenceMappingVisitor);
return referenceMappingVisitor.getResult();
}

/**
* Build the reference {@code reference} based on the {@code fullUrlString} value.
*
* @see https://www.hl7.org/fhir/r4/bundle.html#references
* @param reference
* @param fullUrlString
* @throws URISyntaxException
*/
public static String buildBundleReference(Reference ref, String fullUrlString) {
String referenceUriString = null;
if (ref.getReference() != null && ref.getReference().getValue() != null) {
referenceUriString = ref.getReference().getValue();
if (fullUrlString != null) {
try {
URI referenceUri = new URI(referenceUriString);
if (!referenceUri.isAbsolute() && !referenceUriString.startsWith("#")) {
URI fullUrl = new URI(fullUrlString);
if (fullUrl.isAbsolute()) {
// If the fullUrl of the resource that contains the reference is a RESTful one
// (see the RESTful URL regex), extract the [base], and append the reference to it.
Matcher restUrlMatcher = REFERENCE_PATTERN.matcher(fullUrlString);
if (restUrlMatcher.matches() && restUrlMatcher.groupCount() > 0) {
String urlBase = restUrlMatcher.group(1);
referenceUriString = urlBase + referenceUriString;
}
}
}
} catch (URISyntaxException e) {
if (log.isLoggable(Level.FINE)) {
log.fine("Reference string or fullUrlString is not a valid URI; returning '" + referenceUriString + "'");
}
}
}
}
return referenceUriString;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2019
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -25,19 +25,22 @@
public class ReferenceMappingVisitor<T extends Visitable> extends CopyingVisitor<T> {
private static final Logger log = java.util.logging.Logger.getLogger(ReferenceMappingVisitor.class.getName());
private Map<String, String> localRefMap;
private String localIdentifier;

/**
* @param localRefMap a mapping from the current Reference values to the desired ones
* @param localIdentifier a bundle entry fullUrl value - potentially used with relative references
*/
public ReferenceMappingVisitor(Map<String, String> localRefMap) {
public ReferenceMappingVisitor(Map<String, String> localRefMap, String localIdentifier) {
super();
this.localRefMap = localRefMap;
this.localIdentifier = localIdentifier;
}

@Override
public boolean visit(String elementName, int elementIndex, Reference reference) {
if (reference != null && reference.getReference() != null && reference.getReference().hasValue()) {
String refValue = reference.getReference().getValue();
String refValue = FHIRUtil.buildBundleReference(reference, localIdentifier);
String newRefValue = localRefMap.get(refValue);
if (newRefValue != null) {
if (log.isLoggable(Level.FINER)) {
Expand All @@ -47,7 +50,7 @@ public boolean visit(String elementName, int elementIndex, Reference reference)
markDirty();
} else {
if (log.isLoggable(Level.FINER)) {
log.finer("Reference '" + refValue + "' is not replaced "
log.finer("Reference '" + reference.getReference().getValue() + "' is not replaced "
+ "because it was not found in the local reference map");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

package com.ibm.fhir.model.util.test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotEquals;
import static org.testng.AssertJUnit.assertNotSame;

import java.io.StringWriter;
import java.time.LocalDate;
Expand Down Expand Up @@ -37,8 +37,7 @@
public class ReferenceMappingVisitorTest {
public boolean DEBUG = false;

private Patient patient;
private HashMap<java.lang.String, java.lang.String> localRefMap;
private Patient basePatient;

@BeforeClass
public void setUp() throws Exception {
Expand Down Expand Up @@ -70,45 +69,211 @@ public void setUp() throws Exception {
.family(String.of("Doe"))
.build();

java.lang.String uUID = UUID.randomUUID().toString();

localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("urn:uuid:" + uUID, "urn:romote:" + uUID);

Reference providerRef = Reference.builder()
.reference(String.of("urn:uuid:" + uUID))
.build();

patient = Patient.builder()
basePatient = Patient.builder()
.id(id)
.active(Boolean.TRUE)
.multipleBirth(Integer.of(2))
.meta(meta)
.name(name)
.birthDate(Date.of(LocalDate.now()))
.generalPractitioner(providerRef)
.build();
}

@Test(enabled = true)
@Test
public void testUpdateReferences() throws FHIRGeneratorException {
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap);
java.lang.String uUID = UUID.randomUUID().toString();
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("urn:uuid:" + uUID, "urn:remote:" + uUID);

Reference providerRef = Reference.builder()
.reference(String.of("urn:uuid:" + uUID))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap, null);
patient.accept(visitor);
Patient result = visitor.getResult();

assertNotSame(result, patient);
StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertNotEquals(writer2.toString(), writer1.toString());
assertEquals(result.getGeneralPractitioner().get(0).getReference().getValue(), "urn:remote:" + uUID);
}

@Test
public void testUpdateReferencesWithAbsoluteFullUrlAndRelativeReferenceMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("https://test.com/fhir-server/api/v4/Practitioner/test", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("Practitioner/test"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap,
"https://test.com/fhir-server/api/v4/Patient/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertNotEquals(writer2.toString(), writer1.toString());
assertEquals(result.getGeneralPractitioner().get(0).getReference().getValue(), "Practitioner/1");
}

@Test
public void testUpdateReferencesWithRelativeFullUrlAndAbsoluteReferenceMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("https://test.com/fhir-server/api/v4/Practitioner/test", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("https://test.com/fhir-server/api/v4/Practitioner/test"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap, "Patient/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertNotEquals(writer2.toString(), writer1.toString());
assertEquals(result.getGeneralPractitioner().get(0).getReference().getValue(), "Practitioner/1");
}

@Test
public void testUpdateReferencesWithRelativeFullUrlAndRelativeReferenceMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("Practitioner/test", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("Practitioner/test"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap, "Patient/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertNotEquals(writer2.toString(), writer1.toString());
assertEquals(result.getGeneralPractitioner().get(0).getReference().getValue(), "Practitioner/1");
}

@Test
public void testUpdateReferencesWithAbsoluteFullUrlAndAbsoluteReferenceNoMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("https://test.com/fhir-server/api/v4/Practitioner/test", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("https://test2.com/fhir-server/api/v4/Practitioner/test"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap,
"https://test.com/fhir-server/api/v4/Patient/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertEquals(writer2.toString(), writer1.toString());
}

@Test
public void testUpdateReferencesWithAbsoluteFullUrlAndFragmentReferenceNoMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("https://test.com/fhir-server/api/v4/#Practitioner", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("#Practitioner"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap,
"https://test.com/fhir-server/api/v4/Patient/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertEquals(writer2.toString(), writer1.toString());
}

@Test
public void testUpdateReferencesWithNonRestfulAbsoluteFullUrlAndRelativeReferenceNoMatch() throws FHIRGeneratorException {
HashMap<java.lang.String, java.lang.String> localRefMap = new HashMap<java.lang.String, java.lang.String>();
localRefMap.put("https://test.com/fhir-server/api/v4/Practitioner/test", "Practitioner/1");

Reference providerRef = Reference.builder()
.reference(String.of("Practitioner/test"))
.build();

Patient patient = basePatient.toBuilder().generalPractitioner(providerRef).build();
ReferenceMappingVisitor<Patient> visitor = new ReferenceMappingVisitor<Patient>(localRefMap,
"https://test.com/fhir-server/api/v4/NotValidRestfulRegex/test");
patient.accept(visitor);
Patient result = visitor.getResult();

StringWriter writer1 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(patient, writer1);
StringWriter writer2 = new StringWriter();
FHIRGenerator.generator(Format.JSON, true).generate(result, writer2);

if (DEBUG) {
System.out.println(writer1.toString());
System.out.println(writer2.toString());
}

assertNotEquals(result, patient);
assertEquals(writer2.toString(), writer1.toString());
}
}
Loading

0 comments on commit 24f03c5

Please sign in to comment.