Merge remote-tracking branch 'origin/master' into 2325-channel-import-mimetype
This commit is contained in:
commit
3cc9d08ebc
|
@ -8,6 +8,7 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.junit.jupiter.api.*; import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -99,6 +100,7 @@ public class GenericClientDstu3IT {
|
|||
.build();
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
@ -131,6 +133,8 @@ public class GenericClientDstu3IT {
|
|||
ArgumentCaptor<Request> capt = prepareClientForSearchResponse();
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
|
||||
int idx = 0;
|
||||
|
||||
client
|
||||
|
@ -143,7 +147,7 @@ public class GenericClientDstu3IT {
|
|||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(idx).url().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
@ -170,6 +174,7 @@ public class GenericClientDstu3IT {
|
|||
.build();
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
|
||||
Binary bin = new Binary();
|
||||
bin.setContent(new byte[] { 0, 1, 2, 3, 4 });
|
||||
|
@ -178,7 +183,7 @@ public class GenericClientDstu3IT {
|
|||
Request request = capt.getAllValues().get(0);
|
||||
ourLog.info(request.headers().toString());
|
||||
|
||||
assertEquals("http://example.com/fhir/Binary", request.url().toString());
|
||||
assertEquals("http://example.com/fhir/Binary?_format=json", request.url().toString());
|
||||
validateUserAgent(capt);
|
||||
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
|
||||
|
@ -242,6 +247,7 @@ public class GenericClientDstu3IT {
|
|||
.build();
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
@ -251,8 +257,9 @@ public class GenericClientDstu3IT {
|
|||
assertNull(outcome.getOperationOutcome());
|
||||
assertNotNull(outcome.getResource());
|
||||
|
||||
assertEquals(1, capt.getAllValues().size());
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(0).url().toString());
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,673 +278,6 @@ public class GenericClientDstu3IT {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testForceConformance() throws Exception {
|
||||
final IParser p = ourCtx.newJsonParser();
|
||||
|
||||
final Conformance conf = new Conformance();
|
||||
conf.setCopyright("COPY");
|
||||
|
||||
final Patient patient = new Patient();
|
||||
patient.addName().addFamily("FAM");
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.execute().body()).thenReturn(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_XML + "; charset=UTF-8"), respString));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
private int myCount = 0;
|
||||
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
final String respString;
|
||||
if (myCount == 1 || myCount == 2) {
|
||||
ourLog.info("Encoding patient");
|
||||
respString = p.encodeResourceToString(patient);
|
||||
} else {
|
||||
ourLog.info("Encoding conformance");
|
||||
respString = p.encodeResourceToString(conf);
|
||||
}
|
||||
myCount++;
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://testForceConformance.com/fhir");
|
||||
|
||||
client.read().resource("Patient").withId("1").execute();
|
||||
assertEquals(2, capt.getAllValues().size());
|
||||
assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||
assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(1).getURI().toASCIIString());
|
||||
|
||||
client.read().resource("Patient").withId("1").execute();
|
||||
assertEquals(3, capt.getAllValues().size());
|
||||
assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(2).getURI().toASCIIString());
|
||||
|
||||
client.forceConformanceCheck();
|
||||
assertEquals(4, capt.getAllValues().size());
|
||||
assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(3).getURI().toASCIIString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttp499() throws Exception {
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 499, "Wacky Message"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader("HELLO"), StandardCharsets.UTF_8);
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
try {
|
||||
client.read().resource(Patient.class).withId("1").execute();
|
||||
fail();
|
||||
} catch (UnclassifiedServerFailureException e) {
|
||||
assertEquals("ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException: HTTP 499 Wacky Message", e.toString());
|
||||
assertEquals("HELLO", e.getResponseBody());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttp501() throws Exception {
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 501, "Not Implemented"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader("not implemented"), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
try {
|
||||
client.read().resource(Patient.class).withId("1").execute();
|
||||
fail();
|
||||
} catch (NotImplementedOperationException e) {
|
||||
assertEquals("HTTP 501 Not Implemented", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testInvalidConformanceCall() {
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
try {
|
||||
client.conformance();
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Must call fetchConformance() instead of conformance() for RI/STU3+ structures", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testPutDoesntForceAllIdsJson() throws Exception {
|
||||
IParser p = ourCtx.newJsonParser();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("PATIENT1");
|
||||
patient.addName().addFamily("PATIENT1");
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setId("BUNDLE1");
|
||||
bundle.addEntry().setResource(patient);
|
||||
|
||||
final String encoded = p.encodeResourceToString(bundle);
|
||||
assertEquals("{\"resourceType\":\"Bundle\",\"id\":\"BUNDLE1\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"PATIENT1\",\"name\":[{\"family\":[\"PATIENT1\"]}]}}]}", encoded);
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
//@formatter:off
|
||||
client
|
||||
.update()
|
||||
.resource(bundle)
|
||||
.prefer(PreferReturnEnum.REPRESENTATION)
|
||||
.encodedJson()
|
||||
.execute();
|
||||
//@formatter:on
|
||||
|
||||
HttpPut httpRequest = (HttpPut) capt.getValue();
|
||||
assertEquals("http://example.com/fhir/Bundle/BUNDLE1?_format=json", httpRequest.getURI().toASCIIString());
|
||||
|
||||
String requestString = IOUtils.toString(httpRequest.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
assertEquals(encoded, requestString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseHasContentTypeMissing() throws Exception {
|
||||
IParser p = ourCtx.newJsonParser();
|
||||
Patient patient = new Patient();
|
||||
patient.addName().addFamily("FAM");
|
||||
final String respString = p.encodeResourceToString(patient);
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(null);
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
try {
|
||||
client.read().resource(Patient.class).withId("1").execute();
|
||||
fail();
|
||||
} catch (NonFhirResponseException e) {
|
||||
assertEquals("Response contains no Content-Type", e.getMessage());
|
||||
}
|
||||
|
||||
// Patient resp = client.read().resource(Patient.class).withId("1").execute();
|
||||
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseHasContentTypeNonFhir() throws Exception {
|
||||
IParser p = ourCtx.newJsonParser();
|
||||
Patient patient = new Patient();
|
||||
patient.addName().addFamily("FAM");
|
||||
final String respString = p.encodeResourceToString(patient);
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "text/plain"));
|
||||
// when(myHttpResponse.getEntity().getContentType()).thenReturn(null);
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
try {
|
||||
client.read().resource(Patient.class).withId("1").execute();
|
||||
fail();
|
||||
} catch (NonFhirResponseException e) {
|
||||
assertEquals("Response contains non FHIR Content-Type 'text/plain' : {\"resourceType\":\"Patient\",\"name\":[{\"family\":[\"FAM\"]}]}", e.getMessage());
|
||||
}
|
||||
|
||||
// Patient resp = client.read().resource(Patient.class).withId("1").execute();
|
||||
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchByDate() throws Exception {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
DateTimeDt now = DateTimeDt.withCurrentTime();
|
||||
String dateString = now.getValueAsString().substring(0, 10);
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.BIRTHDATE.after().day(dateString))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?birthdate=gt"+dateString + "&_format=json", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchByString() throws Exception {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().value("AAA"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().value(new StringDt("AAA")))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().values("AAA", "BBB"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().values(Arrays.asList("AAA", "BBB")))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matchesExactly().value("AAA"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matchesExactly().value(new StringDt("AAA")))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchByUrl() throws Exception {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
//@formatter:off
|
||||
client.search()
|
||||
.forResource("Device")
|
||||
.where(Device.URL.matches().value("http://foo.com"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Device?url=http://foo.com&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptHeaderWithEncodingSpecified() throws Exception {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
//@formatter:off
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
client.search()
|
||||
.forResource("Device")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
|
||||
assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
|
||||
idx++;
|
||||
|
||||
//@formatter:off
|
||||
client.setEncoding(EncodingEnum.XML);
|
||||
client.search()
|
||||
.forResource("Device")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
|
||||
assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchForUnknownType() throws Exception {
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
try {
|
||||
client.search(new UriDt("?aaaa"));
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Unable to determine the resource type from the given URI: ?aaaa", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionWithInvalidBody() throws Exception {
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
// Transaction
|
||||
try {
|
||||
client.transaction().withBundle("FOO");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
|
||||
}
|
||||
|
||||
// Create
|
||||
try {
|
||||
client.create().resource("FOO").execute();
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
|
||||
}
|
||||
|
||||
// Update
|
||||
try {
|
||||
client.update().resource("FOO").execute();
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
|
||||
}
|
||||
|
||||
// Validate
|
||||
try {
|
||||
client.validate().resource("FOO").execute();
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//TODO: narratives don't work without stax
|
||||
@Test
|
||||
@Disabled
|
||||
public void testUpdateById() throws Exception {
|
||||
IParser p = ourCtx.newJsonParser();
|
||||
|
||||
OperationOutcome conf = new OperationOutcome();
|
||||
conf.getText().setDivAsString("OK!");
|
||||
|
||||
final String respString = p.encodeResourceToString(conf);
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId("222");
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
||||
client.update().resource(pt).withId("111").execute();
|
||||
|
||||
ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString());
|
||||
|
||||
assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||
validateUserAgent(capt);
|
||||
|
||||
assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", ""));
|
||||
assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue());
|
||||
String body = extractBodyAsString(capt);
|
||||
assertThat(body, containsString("<id value=\"111\"/>"));
|
||||
}
|
||||
|
||||
// TODO: narratives don't work without stax
|
||||
@Test
|
||||
@Disabled
|
||||
public void testUpdateWithPreferRepresentationServerReturnsOO() throws Exception {
|
||||
final IParser p = ourCtx.newJsonParser();
|
||||
|
||||
final OperationOutcome resp0 = new OperationOutcome();
|
||||
resp0.getText().setDivAsString("OK!");
|
||||
|
||||
final Patient resp1 = new Patient();
|
||||
resp1.getText().setDivAsString("FINAL VALUE");
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
|
||||
@Override
|
||||
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") };
|
||||
}
|
||||
});
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
if (myAnswerCount++ == 0) {
|
||||
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8"));
|
||||
} else {
|
||||
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId("Patient/222");
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
||||
MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute();
|
||||
|
||||
assertEquals(2, myAnswerCount);
|
||||
assertNotNull(outcome.getOperationOutcome());
|
||||
assertNotNull(outcome.getResource());
|
||||
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">OK!</div>", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString());
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
|
||||
|
||||
assertEquals(myAnswerCount, capt.getAllValues().size());
|
||||
assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||
assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithPreferRepresentationServerReturnsResource() throws Exception {
|
||||
final IParser p = ourCtx.newJsonParser();
|
||||
|
||||
final Patient resp1 = new Patient();
|
||||
resp1.setActive(true);
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
|
||||
@Override
|
||||
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") };
|
||||
}
|
||||
});
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
myAnswerCount++;
|
||||
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId("Patient/222");
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
||||
MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute();
|
||||
|
||||
assertEquals(1, myAnswerCount);
|
||||
assertNull(outcome.getOperationOutcome());
|
||||
assertNotNull(outcome.getResource());
|
||||
|
||||
assertEquals(true, ((Patient) outcome.getResource()).getActive());
|
||||
|
||||
assertEquals(myAnswerCount, capt.getAllValues().size());
|
||||
assertEquals("http://example.com/fhir/Patient/222?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUserAgentForConformance() throws Exception {
|
||||
IParser p = ourCtx.newJsonParser();
|
||||
|
||||
Conformance conf = new Conformance();
|
||||
conf.setCopyright("COPY");
|
||||
|
||||
final String respString = p.encodeResourceToString(conf);
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
client.fetchConformance().ofType(Conformance.class).execute();
|
||||
assertEquals("http://example.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||
validateUserAgent(capt);
|
||||
}
|
||||
|
||||
|
||||
// TODO: narratives don't work without stax
|
||||
@Test
|
||||
@Disabled
|
||||
public void testValidate() throws Exception {
|
||||
final IParser p = ourCtx.newXmlParser();
|
||||
|
||||
final OperationOutcome resp0 = new OperationOutcome();
|
||||
resp0.getText().setDivAsString("OK!");
|
||||
|
||||
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
|
||||
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.execute().code()).thenReturn(200);
|
||||
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
|
||||
@Override
|
||||
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new Header[] {};
|
||||
}
|
||||
});
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId("Patient/222");
|
||||
pt.getText().setDivAsString("A PATIENT");
|
||||
|
||||
MethodOutcome outcome = client.validate().resource(pt).execute();
|
||||
|
||||
assertNotNull(outcome.getOperationOutcome());
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">OK!</div>", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@AfterAll
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.interceptor.api;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ public class BundleBuilder {
|
|||
*
|
||||
* @param theResource The resource to create
|
||||
*/
|
||||
public CreateBuilder addCreateEntry(IBaseResource theResource) {
|
||||
public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) {
|
||||
setBundleField("type", "transaction");
|
||||
|
||||
IBase request = addEntryAndReturnRequest(theResource);
|
||||
|
@ -338,7 +338,6 @@ public class BundleBuilder {
|
|||
setBundleField("type", theType);
|
||||
}
|
||||
|
||||
|
||||
public static class UpdateBuilder {
|
||||
|
||||
private final IPrimitiveType<?> myUrl;
|
||||
|
|
|
@ -108,6 +108,8 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully delet
|
|||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSortParameter=Unknown _sort parameter value "{0}" for resource type "{1}" (Note: sort parameters values must use a valid Search Parameter). Valid values for this search are: {2}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidBundleTypeForStorage=Unable to store a Bundle resource on this server with a Bundle.type value of: {0}. Note that if you are trying to perform a FHIR 'transaction' or 'batch' operation you should POST the Bundle resource to the Base URL of the server, not to the '/Bundle' endpoint.
|
||||
|
||||
ca.uhn.fhir.rest.api.PatchTypeEnum.missingPatchContentType=Missing or invalid content type for PATCH operation
|
||||
ca.uhn.fhir.rest.api.PatchTypeEnum.invalidPatchContentType=Invalid Content-Type for PATCH operation: {0}
|
||||
ca.uhn.fhir.jpa.dao.BaseTransactionProcessor.unsupportedResourceType=Resource {0} is not supported on this server. Supported resource types: {1}
|
||||
|
|
|
@ -683,7 +683,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
|
||||
|
||||
Map<String, List<String>> params = new HashMap<String, List<String>>();
|
||||
Map<String, List<String>> params = new HashMap<>();
|
||||
return invoke(params, binding, invocation);
|
||||
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ import ca.uhn.fhir.util.FhirTerser;
|
|||
public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulClientFactory.class);
|
||||
|
||||
private Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<String>());
|
||||
private final Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<>());
|
||||
private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
|
||||
private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
private FhirContext myContext;
|
||||
private Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<>();
|
||||
private final Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<>();
|
||||
private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
|
||||
private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
|
||||
private String myProxyUsername;
|
||||
|
@ -316,7 +316,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
String serverFhirVersionString = null;
|
||||
Object value = t.getSingleValueOrNull(conformance, "fhirVersion");
|
||||
if (value instanceof IPrimitiveType) {
|
||||
serverFhirVersionString = IPrimitiveType.class.cast(value).getValueAsString();
|
||||
serverFhirVersionString = ((IPrimitiveType<?>) value).getValueAsString();
|
||||
}
|
||||
FhirVersionEnum serverFhirVersionEnum = null;
|
||||
if (StringUtils.isBlank(serverFhirVersionString)) {
|
||||
|
|
|
@ -84,7 +84,7 @@ public class BundleBuilderExamples {
|
|||
patient.setActive(true);
|
||||
|
||||
// Add the patient as a create (aka POST) to the Bundle
|
||||
builder.addCreateEntry(patient);
|
||||
builder.addTransactionCreateEntry(patient);
|
||||
|
||||
// Execute the transaction
|
||||
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
||||
|
@ -102,7 +102,7 @@ public class BundleBuilderExamples {
|
|||
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||
|
||||
// Add the patient as a create (aka POST) to the Bundle
|
||||
builder.addCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
||||
builder.addTransactionCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
||||
|
||||
// Execute the transaction
|
||||
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2323
|
||||
title: "The JPA server has a new setting on the `ModelConfig` bean called \"AutoVersionReferencesAtPaths\". Using
|
||||
this setting, the server can be configured to add the current target resource version ID to any resource
|
||||
references found in a resource being stored. In addition, a new setting has been added to the JPA ModelConfig
|
||||
bean that allows `_include` statements to respect versioned references, and actually include the correct
|
||||
version for the reference."
|
|
@ -240,7 +240,7 @@ Use the `$mdm-update-link` operation to change the `matchResult` update of an md
|
|||
<td>String</td>
|
||||
<td>1..1</td>
|
||||
<td>
|
||||
The id of the source resource.
|
||||
The id of the target resource.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -318,6 +318,14 @@ This operation takes the following parameters:
|
|||
The id of the Golden Resource to merge data into.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>resource</td>
|
||||
<td>Resource</td>
|
||||
<td>0..1</td>
|
||||
<td>
|
||||
Optional manually merged Golden Resource. All values except for the metadata, PID and identifiers will be copied from this resource, if it is present. If no value is specified, all fields from the resource pointed to by "fromGoldenResourceId" will be copied instead.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -356,6 +356,14 @@ The following algorithms are currently supported:
|
|||
</td>
|
||||
<td>If an optional "identifierSystem" is provided, then the identifiers only match when they belong to that system</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>EMPTY_FIELD</td>
|
||||
<td>matcher</td>
|
||||
<td>
|
||||
Matches an empty field.
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>JARO_WINKLER</td>
|
||||
<td>similarity</td>
|
||||
|
@ -395,7 +403,7 @@ The following algorithms are currently supported:
|
|||
<a href="https://github.com/tdebatty/java-string-similarity#sorensen-dice-coefficient">tdebatty Sorensen-Dice coefficient</a>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -265,9 +265,11 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
*/
|
||||
DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest);
|
||||
|
||||
// /**
|
||||
// * Invoke the everything operation
|
||||
// */
|
||||
// IBundleProvider everything(IIdType theId);
|
||||
/**
|
||||
* Returns the current version ID for the given resource
|
||||
*/
|
||||
default String getCurrentVersionId(IIdType theReferenceElement) {
|
||||
return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,8 +47,9 @@ public class DaoMethodOutcome extends MethodOutcome {
|
|||
/**
|
||||
* Was this a NO-OP - Typically because of an update to a resource that already matched the contents provided
|
||||
*/
|
||||
public void setNop(boolean theNop) {
|
||||
public DaoMethodOutcome setNop(boolean theNop) {
|
||||
myNop = theNop;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IBasePersistedResource getEntity() {
|
||||
|
|
|
@ -120,6 +120,7 @@ import javax.annotation.PostConstruct;
|
|||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -259,6 +260,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
StopWatch w = new StopWatch();
|
||||
|
||||
preProcessResourceForStorage(theResource);
|
||||
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
ResourceTable entity = new ResourceTable();
|
||||
entity.setResourceType(toResourceName(theResource));
|
||||
|
@ -274,7 +276,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||
IBaseResource resource = toResource(entity, false);
|
||||
theResource.setId(resource.getIdElement().getValue());
|
||||
return toMethodOutcome(theRequest, entity, resource).setCreated(false);
|
||||
return toMethodOutcome(theRequest, entity, resource).setCreated(false).setNop(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1129,6 +1131,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return readEntity(theId, true, theRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String getCurrentVersionId(IIdType theReferenceElement) {
|
||||
return Long.toString(readEntity(theReferenceElement.toVersionless(), null).getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||
|
@ -1459,6 +1467,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
T resource = theResource;
|
||||
|
||||
preProcessResourceForStorage(resource);
|
||||
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
final ResourceTable entity;
|
||||
|
||||
|
@ -1529,6 +1538,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
resource.setId(entity.getIdDt().getValue());
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
|
||||
outcome.setPreviousResource(oldResource);
|
||||
if (!outcome.isNop()) {
|
||||
outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1)));
|
||||
}
|
||||
return outcome;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,11 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
|||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
|
@ -53,11 +56,10 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
|
|||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -67,6 +69,7 @@ import javax.annotation.Nullable;
|
|||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -77,30 +80,85 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseStorageDao {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseStorageDao.class);
|
||||
@Autowired
|
||||
protected ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
protected FhirContext myFhirContext;
|
||||
@Autowired
|
||||
protected DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
protected ModelConfig myModelConfig;
|
||||
|
||||
/**
|
||||
* May be overridden by subclasses to validate resources prior to storage
|
||||
*
|
||||
* @param theResource The resource that is about to be stored
|
||||
* @deprecated Use {@link #preProcessResourceForStorage(IBaseResource, RequestDetails, TransactionDetails, boolean)} instead
|
||||
*/
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* May be overridden by subclasses to validate resources prior to storage
|
||||
*
|
||||
* @param theResource The resource that is about to be stored
|
||||
* @since 5.3.0
|
||||
*/
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails, boolean thePerformIndexing) {
|
||||
|
||||
verifyResourceTypeIsAppropriateForDao(theResource);
|
||||
|
||||
verifyResourceIdIsValid(theResource);
|
||||
|
||||
verifyBundleTypeIsAppropriateForStorage(theResource);
|
||||
|
||||
replaceAbsoluteReferencesWithRelative(theResource);
|
||||
|
||||
performAutoVersioning(theResource, thePerformIndexing);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity check - Is this resource the right type for this DAO?
|
||||
*/
|
||||
private void verifyResourceTypeIsAppropriateForDao(IBaseResource theResource) {
|
||||
String type = getContext().getResourceType(theResource);
|
||||
if (getResourceName() != null && !getResourceName().equals(type)) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the resource ID is actually valid according to FHIR's rules
|
||||
*/
|
||||
private void verifyResourceIdIsValid(IBaseResource theResource) {
|
||||
if (theResource.getIdElement().hasIdPart()) {
|
||||
if (!theResource.getIdElement().isIdPartValid()) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace absolute references with relative ones if configured to do so
|
||||
*/
|
||||
/**
|
||||
* Verify that we're not storing a Bundle with a disallowed bundle type
|
||||
*/
|
||||
private void verifyBundleTypeIsAppropriateForStorage(IBaseResource theResource) {
|
||||
if (theResource instanceof IBaseBundle) {
|
||||
Set<String> allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage();
|
||||
String bundleType = BundleUtil.getBundleType(getContext(), (IBaseBundle) theResource);
|
||||
bundleType = defaultString(bundleType);
|
||||
if (!allowedBundleTypes.contains(bundleType)) {
|
||||
String message = myFhirContext.getLocalizer().getMessage(BaseStorageDao.class, "invalidBundleTypeForStorage", (isNotBlank(bundleType) ? bundleType : "(missing)"));
|
||||
throw new UnprocessableEntityException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace absolute references with relative ones if configured to do so
|
||||
*/
|
||||
private void replaceAbsoluteReferencesWithRelative(IBaseResource theResource) {
|
||||
if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) {
|
||||
FhirTerser t = getContext().newTerser();
|
||||
List<ResourceReferenceInfo> refs = t.getAllResourceReferences(theResource);
|
||||
|
@ -114,17 +172,38 @@ public abstract class BaseStorageDao {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("Bundle".equals(type)) {
|
||||
Set<String> allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage();
|
||||
String bundleType = BundleUtil.getBundleType(getContext(), (IBaseBundle) theResource);
|
||||
bundleType = defaultString(bundleType);
|
||||
if (!allowedBundleTypes.contains(bundleType)) {
|
||||
String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (isNotBlank(bundleType) ? bundleType : "(missing)");
|
||||
throw new UnprocessableEntityException(message);
|
||||
/**
|
||||
* Handle {@link ModelConfig#getAutoVersionReferenceAtPaths() auto-populate-versions}
|
||||
*
|
||||
* We only do this if thePerformIndexing is true because if it's false, that means
|
||||
* we're in a FHIR transaction during the first phase of write operation processing,
|
||||
* meaning that the versions of other resources may not have need updated yet. For example
|
||||
* we're about to store an Observation with a reference to a Patient, and that Patient
|
||||
* is also being updated in the same transaction, during the first "no index" phase,
|
||||
* the Patient will not yet have its version number incremented, so it would be wrong
|
||||
* to use that value. During the second phase it is correct.
|
||||
*
|
||||
* Also note that {@link BaseTransactionProcessor} also has code to do auto-versioning
|
||||
* and it is the one that takes care of the placeholder IDs. Look for the other caller of
|
||||
* {@link #extractReferencesToAutoVersion(FhirContext, ModelConfig, IBaseResource)}
|
||||
* to find this.
|
||||
*/
|
||||
private void performAutoVersioning(IBaseResource theResource, boolean thePerformIndexing) {
|
||||
if (thePerformIndexing) {
|
||||
Set<IBaseReference> referencesToVersion = extractReferencesToAutoVersion(myFhirContext, myModelConfig, theResource);
|
||||
for (IBaseReference nextReference : referencesToVersion) {
|
||||
IIdType referenceElement = nextReference.getReferenceElement();
|
||||
if (!referenceElement.hasBaseUrl()) {
|
||||
String resourceType = referenceElement.getResourceType();
|
||||
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||
String targetVersionId = dao.getCurrentVersionId(referenceElement);
|
||||
String newTargetReference = referenceElement.withVersion(targetVersionId).getValue();
|
||||
nextReference.setReference(newTargetReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) {
|
||||
|
@ -267,4 +346,28 @@ public abstract class BaseStorageDao {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ModelConfig#getAutoVersionReferenceAtPaths()
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<IBaseReference> extractReferencesToAutoVersion(FhirContext theFhirContext, ModelConfig theModelConfig, IBaseResource theResource) {
|
||||
Map<IBaseReference, Object> references = Collections.emptyMap();
|
||||
if (!theModelConfig.getAutoVersionReferenceAtPaths().isEmpty()) {
|
||||
String resourceName = theFhirContext.getResourceType(theResource);
|
||||
for (String nextPath : theModelConfig.getAutoVersionReferenceAtPathsByResourceType(resourceName)) {
|
||||
List<IBaseReference> nextReferences = theFhirContext.newTerser().getValues(theResource, nextPath, IBaseReference.class);
|
||||
for (IBaseReference next : nextReferences) {
|
||||
if (next.getReferenceElement().hasVersionIdPart()) {
|
||||
continue;
|
||||
}
|
||||
if (references.isEmpty()) {
|
||||
references = new IdentityHashMap<>();
|
||||
}
|
||||
references.put(next, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return references.keySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
|||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
|
@ -78,6 +79,7 @@ import org.hl7.fhir.instance.model.api.IBaseBinary;
|
|||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -130,10 +132,12 @@ public abstract class BaseTransactionProcessor {
|
|||
private HapiTransactionService myHapiTransactionService;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
|
||||
ourLog.trace("Starting transaction processor");
|
||||
}
|
||||
|
||||
public <BUNDLE extends IBaseBundle> BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) {
|
||||
|
@ -195,7 +199,7 @@ public abstract class BaseTransactionProcessor {
|
|||
|
||||
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome,
|
||||
IBase newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
|
||||
IIdType newId = outcome.getId().toUnqualifiedVersionless();
|
||||
IIdType newId = outcome.getId().toUnqualified();
|
||||
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
||||
if (newId.equals(resourceId) == false) {
|
||||
idSubstitutions.put(resourceId, newId);
|
||||
|
@ -900,20 +904,32 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
|
||||
// References
|
||||
Set<IBaseReference> referencesToVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
|
||||
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
||||
for (ResourceReferenceInfo nextRef : allRefs) {
|
||||
IIdType nextId = nextRef.getResourceReference().getReferenceElement();
|
||||
IBaseReference resourceReference = nextRef.getResourceReference();
|
||||
IIdType nextId = resourceReference.getReferenceElement();
|
||||
if (!nextId.hasIdPart()) {
|
||||
continue;
|
||||
}
|
||||
if (theIdSubstitutions.containsKey(nextId)) {
|
||||
IIdType newId = theIdSubstitutions.get(nextId);
|
||||
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
|
||||
nextRef.getResourceReference().setReference(newId.getValue());
|
||||
if (referencesToVersion.contains(resourceReference)) {
|
||||
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(newId);
|
||||
resourceReference.setReference(newId.getValue());
|
||||
} else {
|
||||
resourceReference.setReference(newId.toVersionless().getValue());
|
||||
}
|
||||
} else if (nextId.getValue().startsWith("urn:")) {
|
||||
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
|
||||
} else {
|
||||
ourLog.debug(" * Reference [{}] does not exist in bundle", nextId);
|
||||
if (referencesToVersion.contains(resourceReference)) {
|
||||
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
|
||||
if (!outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
|
||||
resourceReference.setReference(nextId.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -928,7 +944,7 @@ public abstract class BaseTransactionProcessor {
|
|||
if (theIdSubstitutions.containsKey(nextUriString)) {
|
||||
IIdType newId = theIdSubstitutions.get(nextUriString);
|
||||
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
|
||||
nextRef.setValueAsString(newId.getValue());
|
||||
nextRef.setValueAsString(newId.toVersionless().getValue());
|
||||
} else {
|
||||
ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.dao;
|
|||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
|
@ -32,8 +34,8 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
public class FhirResourceDaoBundleDstu2 extends BaseHapiFhirResourceDao<Bundle> {
|
||||
|
||||
@Override
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
||||
super.preProcessResourceForStorage(theResource);
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails, boolean thePerformIndexing) {
|
||||
super.preProcessResourceForStorage(theResource, theRequestDetails, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
for (Entry next : ((Bundle)theResource).getEntry()) {
|
||||
next.setFullUrl((String) null);
|
||||
|
|
|
@ -436,7 +436,13 @@ public class IdHelperService {
|
|||
|
||||
@Nonnull
|
||||
public Long getPidOrThrowException(IAnyResource theResource) {
|
||||
return (Long) theResource.getUserData(RESOURCE_PID);
|
||||
Long retVal = (Long) theResource.getUserData(RESOURCE_PID);
|
||||
if (retVal == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Unable to find %s in the user data for %s with ID %s", RESOURCE_PID, theResource, theResource.getId())
|
||||
);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public IIdType resourceIdFromPidOrThrowException(Long thePid) {
|
||||
|
|
|
@ -29,7 +29,9 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
|
||||
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
||||
|
@ -42,6 +44,8 @@ import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
|||
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
||||
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||
|
@ -87,6 +91,7 @@ import com.healthmarketscience.sqlbuilder.Condition;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -147,6 +152,8 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private IResourceSearchViewDao myResourceSearchViewDao;
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
|
@ -174,8 +181,11 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private SqlObjectFactory mySqlBuilderFactory;
|
||||
@Autowired
|
||||
private HibernatePropertiesProvider myDialectProvider;
|
||||
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
private boolean hasNextIteratorQuery = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -428,7 +438,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
//-- exclude the pids already in the previous iterator
|
||||
if (hasNextIteratorQuery)
|
||||
sqlBuilder.excludeResourceIdsPredicate(myPidSet);
|
||||
|
||||
|
||||
/*
|
||||
* Sort
|
||||
*
|
||||
|
@ -532,23 +542,23 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theSort.getParamName(), ascending);
|
||||
break;
|
||||
break;
|
||||
case COMPOSITE:
|
||||
List<RuntimeSearchParam> compositList = param.getCompositeOf();
|
||||
if (compositList == null) {
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName() + " is not defined by the resource " + myResourceName);
|
||||
}
|
||||
if (compositList.size() != 2) {
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName()
|
||||
+ " must have 2 composite types declared in parameter annotation, found "
|
||||
+ compositList.size());
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName()
|
||||
+ " must have 2 composite types declared in parameter annotation, found "
|
||||
+ compositList.size());
|
||||
}
|
||||
RuntimeSearchParam left = compositList.get(0);
|
||||
RuntimeSearchParam right = compositList.get(1);
|
||||
|
||||
|
||||
createCompositeSort(theQueryStack, myResourceName, left.getParamType(), left.getName(), ascending);
|
||||
createCompositeSort(theQueryStack, myResourceName, right.getParamType(), right.getName(), ascending);
|
||||
|
||||
|
||||
break;
|
||||
case SPECIAL:
|
||||
case HAS:
|
||||
|
@ -562,24 +572,30 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
createSort(theQueryStack, theSort.getChain());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void createCompositeSort(QueryStack theQueryStack, String theResourceName, RestSearchParameterTypeEnum theParamType, String theParamName, boolean theAscending) {
|
||||
|
||||
switch (theParamType) {
|
||||
case STRING:
|
||||
theQueryStack.addSortOnString(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case DATE:
|
||||
theQueryStack.addSortOnDate(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case TOKEN:
|
||||
theQueryStack.addSortOnToken(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParamType + " on _sort="+ theParamName);
|
||||
case STRING:
|
||||
theQueryStack.addSortOnString(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case DATE:
|
||||
theQueryStack.addSortOnDate(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case TOKEN:
|
||||
theQueryStack.addSortOnToken(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case NUMBER:
|
||||
case REFERENCE:
|
||||
case COMPOSITE:
|
||||
case URI:
|
||||
case HAS:
|
||||
case SPECIAL:
|
||||
default:
|
||||
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParamType + " on _sort=" + theParamName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -587,34 +603,61 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private void doLoadPids(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation,
|
||||
Map<ResourcePersistentId, Integer> thePosition) {
|
||||
|
||||
List<Long> myLongPersistentIds;
|
||||
if (thePids.size() < getMaximumPageSize()) {
|
||||
myLongPersistentIds = normalizeIdListForLastNInClause(ResourcePersistentId.toLongList(thePids));
|
||||
} else {
|
||||
myLongPersistentIds = ResourcePersistentId.toLongList(thePids);
|
||||
Map<Long, Long> resourcePidToVersion = null;
|
||||
for (ResourcePersistentId next : thePids) {
|
||||
if (next.getVersion() != null && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||
if (resourcePidToVersion == null) {
|
||||
resourcePidToVersion = new HashMap<>();
|
||||
}
|
||||
resourcePidToVersion.put(next.getIdAsLong(), next.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
List<Long> versionlessPids = ResourcePersistentId.toLongList(thePids);
|
||||
if (versionlessPids.size() < getMaximumPageSize()) {
|
||||
versionlessPids = normalizeIdListForLastNInClause(versionlessPids);
|
||||
}
|
||||
|
||||
// -- get the resource from the searchView
|
||||
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(myLongPersistentIds);
|
||||
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(versionlessPids);
|
||||
|
||||
//-- preload all tags with tag definition if any
|
||||
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
||||
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
||||
|
||||
ResourcePersistentId resourceId;
|
||||
for (ResourceSearchView next : resourceSearchViewList) {
|
||||
for (IBaseResourceEntity next : resourceSearchViewList) {
|
||||
if (next.getDeleted() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
||||
|
||||
resourceId = new ResourcePersistentId(next.getId());
|
||||
ResourcePersistentId resourceId = new ResourcePersistentId(next.getResourceId());
|
||||
|
||||
IBaseResource resource = myCallingDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation);
|
||||
/*
|
||||
* If a specific version is requested via an include, we'll replace the current version
|
||||
* with the specific desired version. This is not the most efficient thing, given that
|
||||
* we're loading the current version and then turning around and throwing it away again.
|
||||
* This could be optimized and probably should be, but it's not critical given that
|
||||
* this only applies to includes, which don't tend to be massive in numbers.
|
||||
*/
|
||||
if (resourcePidToVersion != null) {
|
||||
Long version = resourcePidToVersion.get(next.getResourceId());
|
||||
if (version != null && !version.equals(next.getVersion())) {
|
||||
resourceId.setVersion(version);
|
||||
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||
next = dao.readEntity(next.getIdDt().withVersion(Long.toString(version)), null);
|
||||
}
|
||||
}
|
||||
|
||||
IBaseResource resource = null;
|
||||
if (next != null) {
|
||||
resource = myCallingDao.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation);
|
||||
}
|
||||
if (resource == null) {
|
||||
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer index = thePosition.get(resourceId);
|
||||
if (index == null) {
|
||||
ourLog.warn("Got back unexpected resource PID {}", resourceId);
|
||||
|
@ -639,17 +682,17 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private Map<ResourcePersistentId, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
|
||||
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<? extends IBaseResourceEntity> theResourceSearchViewList) {
|
||||
|
||||
List<Long> idList = new ArrayList<>(theResourceSearchViewList.size());
|
||||
|
||||
//-- find all resource has tags
|
||||
for (ResourceSearchView resource : theResourceSearchViewList) {
|
||||
for (IBaseResourceEntity resource : theResourceSearchViewList) {
|
||||
if (resource.isHasTags())
|
||||
idList.add(resource.getId());
|
||||
}
|
||||
|
||||
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = new HashMap<>();
|
||||
Map<Long, Collection<ResourceTag>> tagMap = new HashMap<>();
|
||||
|
||||
//-- no tags
|
||||
if (idList.size() == 0)
|
||||
|
@ -664,11 +707,11 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
for (ResourceTag tag : tagList) {
|
||||
|
||||
resourceId = new ResourcePersistentId(tag.getResourceId());
|
||||
tagCol = tagMap.get(resourceId);
|
||||
tagCol = tagMap.get(resourceId.getIdAsLong());
|
||||
if (tagCol == null) {
|
||||
tagCol = new ArrayList<>();
|
||||
tagCol.add(tag);
|
||||
tagMap.put(resourceId, tagCol);
|
||||
tagMap.put(resourceId.getIdAsLong(), tagCol);
|
||||
} else {
|
||||
tagCol.add(tag);
|
||||
}
|
||||
|
@ -712,8 +755,12 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
||||
String findFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
||||
String searchPidFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
||||
String findPidFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
||||
String findVersionFieldName = null;
|
||||
if (!theReverseMode && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||
findVersionFieldName = "myTargetResourceVersion";
|
||||
}
|
||||
|
||||
List<ResourcePersistentId> nextRoundMatches = new ArrayList<>(theMatches);
|
||||
HashSet<ResourcePersistentId> allAdded = new HashSet<>();
|
||||
|
@ -737,24 +784,37 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
boolean matchAll = "*".equals(nextInclude.getValue());
|
||||
if (matchAll) {
|
||||
String sql;
|
||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids) ";
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("SELECT r.").append(findPidFieldName);
|
||||
if (findVersionFieldName != null) {
|
||||
sqlBuilder.append(", r." + findVersionFieldName);
|
||||
}
|
||||
sqlBuilder.append(" FROM ResourceLink r WHERE r.");
|
||||
sqlBuilder.append(searchPidFieldName);
|
||||
sqlBuilder.append(" IN (:target_pids)");
|
||||
String sql = sqlBuilder.toString();
|
||||
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
||||
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
|
||||
TypedQuery<?> q = theEntityManager.createQuery(sql, Object[].class);
|
||||
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
||||
List<Long> results = q.getResultList();
|
||||
for (Long resourceLink : results) {
|
||||
if (resourceLink == null) {
|
||||
List<?> results = q.getResultList();
|
||||
for (Object nextRow : results) {
|
||||
if (nextRow == null) {
|
||||
// This can happen if there are outgoing references which are canonical or point to
|
||||
// other servers
|
||||
continue;
|
||||
}
|
||||
if (theReverseMode) {
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
||||
|
||||
Long resourceLink;
|
||||
Long version = null;
|
||||
if (findVersionFieldName != null) {
|
||||
resourceLink = (Long) ((Object[]) nextRow)[0];
|
||||
version = (Long) ((Object[]) nextRow)[1];
|
||||
} else {
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
||||
resourceLink = (Long)nextRow;
|
||||
}
|
||||
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink, version));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -789,17 +849,22 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
String sql;
|
||||
|
||||
boolean haveTargetTypesDefinedByParam = param.hasTargets();
|
||||
String fieldsToLoad = "r." + findPidFieldName;
|
||||
if (findVersionFieldName != null) {
|
||||
fieldsToLoad += ", r." + findVersionFieldName;
|
||||
}
|
||||
|
||||
if (targetResourceType != null) {
|
||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids) AND r.myTargetResourceType = :target_resource_type";
|
||||
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids) AND r.myTargetResourceType = :target_resource_type";
|
||||
} else if (haveTargetTypesDefinedByParam) {
|
||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids) AND r.myTargetResourceType in (:target_resource_types)";
|
||||
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids) AND r.myTargetResourceType in (:target_resource_types)";
|
||||
} else {
|
||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
|
||||
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids)";
|
||||
}
|
||||
|
||||
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
||||
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
|
||||
TypedQuery<?> q = theEntityManager.createQuery(sql, Object[].class);
|
||||
q.setParameter("src_path", nextPath);
|
||||
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
||||
if (targetResourceType != null) {
|
||||
|
@ -807,10 +872,18 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
} else if (haveTargetTypesDefinedByParam) {
|
||||
q.setParameter("target_resource_types", param.getTargets());
|
||||
}
|
||||
List<Long> results = q.getResultList();
|
||||
for (Long resourceLink : results) {
|
||||
List<?> results = q.getResultList();
|
||||
for (Object resourceLink : results) {
|
||||
if (resourceLink != null) {
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
||||
ResourcePersistentId persistentId;
|
||||
if (findVersionFieldName != null) {
|
||||
persistentId = new ResourcePersistentId(((Object[])resourceLink)[0]);
|
||||
persistentId.setVersion((Long) ((Object[])resourceLink)[1]);
|
||||
} else {
|
||||
persistentId = new ResourcePersistentId(resourceLink);
|
||||
}
|
||||
assert persistentId.getId() instanceof Long;
|
||||
pidsToInclude.add(persistentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1051,6 +1124,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private final boolean myHaveRawSqlHooks;
|
||||
private final boolean myHavePerfTraceFoundIdHook;
|
||||
private final SortSpec mySort;
|
||||
private final Integer myOffset;
|
||||
private boolean myFirst = true;
|
||||
private IncludesIterator myIncludesIterator;
|
||||
private ResourcePersistentId myNext;
|
||||
|
@ -1059,8 +1133,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private boolean myStillNeedToFetchIncludes;
|
||||
private int mySkipCount = 0;
|
||||
private int myNonSkipCount = 0;
|
||||
private final Integer myOffset;
|
||||
|
||||
private ArrayList<SearchQueryExecutor> myQueryList = new ArrayList<>();
|
||||
|
||||
private QueryIterator(SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
|
||||
|
@ -1249,7 +1321,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
if (myNext == null) {
|
||||
fetchNext();
|
||||
}
|
||||
return !NO_MORE.equals(myNext);
|
||||
return !NO_MORE.equals(myNext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
|
||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
|
@ -50,6 +51,9 @@ public class TransactionProcessorTest {
|
|||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
@MockBean
|
||||
private HapiTransactionService myHapiTransactionService;
|
||||
@MockBean
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private SessionImpl mySession;
|
||||
|
||||
|
|
|
@ -389,7 +389,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
@ -399,7 +399,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: batch-response", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: batch-response. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
|
|
@ -4,8 +4,6 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
|
|
|
@ -526,7 +526,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
@ -536,7 +536,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
|
|
@ -2985,7 +2985,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
dr.addPresentedForm(attachment);
|
||||
|
||||
Attachment attachment2 = new Attachment();
|
||||
attachment2.setUrl(IdType.newRandomUuid().getValue()); // this one has no subscitution
|
||||
attachment2.setUrl(IdType.newRandomUuid().getValue()); // this one has no substitution
|
||||
dr.addPresentedForm(attachment2);
|
||||
|
||||
Bundle transactionBundle = new Bundle();
|
||||
|
|
|
@ -21,6 +21,7 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test {
|
|||
public void afterResetConfig() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnWrite(new DaoConfig().isEnforceReferentialIntegrityOnWrite());
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
|
||||
myDaoConfig.setEnforceReferenceTargetTypes(new DaoConfig().isEnforceReferenceTargetTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -38,7 +39,7 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUnknownReferenceAllow() throws Exception {
|
||||
public void testCreateUnknownReferenceAllowed() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnWrite(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
|
@ -50,6 +51,20 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUnknownReferenceAllowed_NumericId() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnWrite(false);
|
||||
myDaoConfig.setEnforceReferenceTargetTypes(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setManagingOrganization(new Reference("Organization/123"));
|
||||
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
p = myPatientDao.read(id);
|
||||
assertEquals("Organization/123", p.getManagingOrganization().getReference());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteFail() throws Exception {
|
||||
Organization o = new Organization();
|
||||
|
|
|
@ -842,7 +842,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
@ -852,7 +852,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset", e.getMessage());
|
||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
|
|
|
@ -0,0 +1,482 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.util.BundleBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4VersionedReferenceTest.class);
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
|
||||
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveVersionedReference() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
|
||||
assertEquals("1", patientId.getVersionIdPart());
|
||||
assertEquals(null, patientId.getBaseUrl());
|
||||
String patientIdString = patientId.getValue();
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientIdString);
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Read back
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontOverwriteExistingVersion() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
myPatientDao.create(p);
|
||||
|
||||
// Update the patient
|
||||
p.setActive(false);
|
||||
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
assertEquals(null, patientId.getBaseUrl());
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Read back
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientId.withVersion("1").getValue(), observation.getSubject().getReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertVersionedReferenceAtPath() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
|
||||
assertEquals("1", patientId.getVersionIdPart());
|
||||
assertEquals(null, patientId.getBaseUrl());
|
||||
String patientIdString = patientId.getValue();
|
||||
|
||||
// Create - put an unversioned reference in the subject
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.toVersionless().getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
|
||||
// Update - put an unversioned reference in the subject
|
||||
observation = new Observation();
|
||||
observation.setId(observationId);
|
||||
observation.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||
observation.getSubject().setReference(patientId.toVersionless().getValue());
|
||||
myObservationDao.update(observation);
|
||||
|
||||
// Make sure we're not introducing any extra DB operations
|
||||
assertEquals(5, myCaptureQueriesListener.logSelectQueries().size());
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertVersionedReferenceAtPath_InTransaction_SourceAndTargetBothCreated() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||
|
||||
|
||||
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
patient.setActive(true);
|
||||
builder.addTransactionCreateEntry(patient);
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setId(IdType.newRandomUuid());
|
||||
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||
builder.addTransactionCreateEntry(encounter);
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patient.getId()); // versioned
|
||||
observation.getEncounter().setReference(encounter.getId()); // not versioned
|
||||
builder.addTransactionCreateEntry(observation);
|
||||
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
|
||||
assertTrue(patientId.hasVersionIdPart());
|
||||
assertTrue(encounterId.hasVersionIdPart());
|
||||
assertTrue(observationId.hasVersionIdPart());
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertVersionedReferenceAtPath_InTransaction_TargetConditionalCreatedNop() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||
|
||||
{
|
||||
// Create patient
|
||||
Patient patient = new Patient();
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
patient.setActive(true);
|
||||
myPatientDao.create(patient).getId();
|
||||
|
||||
// Update patient to make a second version
|
||||
patient.setActive(false);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
// Create encounter
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setId(IdType.newRandomUuid());
|
||||
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||
myEncounterDao.create(encounter);
|
||||
}
|
||||
|
||||
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
patient.setActive(true);
|
||||
builder.addTransactionCreateEntry(patient).conditional("Patient?active=false");
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setId(IdType.newRandomUuid());
|
||||
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||
builder.addTransactionCreateEntry(encounter).conditional("Encounter?identifier=http://baz|baz");
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patient.getId()); // versioned
|
||||
observation.getEncounter().setReference(encounter.getId()); // not versioned
|
||||
builder.addTransactionCreateEntry(observation);
|
||||
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("200 OK", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
assertEquals("201 Created", outcome.getEntry().get(2).getResponse().getStatus());
|
||||
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
assertEquals("1", encounterId.getVersionIdPart());
|
||||
assertEquals("1", observationId.getVersionIdPart());
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||
|
||||
{
|
||||
// Create patient
|
||||
Patient patient = new Patient();
|
||||
patient.setId("PATIENT");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient).getId();
|
||||
|
||||
// Update patient to make a second version
|
||||
patient.setActive(false);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
}
|
||||
|
||||
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/PATIENT");
|
||||
patient.setActive(true);
|
||||
builder.addTransactionUpdateEntry(patient);
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patient.getId()); // versioned
|
||||
builder.addTransactionCreateEntry(observation);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("3", patientId.getVersionIdPart());
|
||||
assertEquals("1", observationId.getVersionIdPart());
|
||||
|
||||
// Make sure we're not introducing any extra DB operations
|
||||
assertEquals(3, myCaptureQueriesListener.logSelectQueries().size());
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditional() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||
|
||||
{
|
||||
// Create patient
|
||||
Patient patient = new Patient();
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
patient.setActive(true);
|
||||
myPatientDao.create(patient).getId();
|
||||
|
||||
// Update patient to make a second version
|
||||
patient.setActive(false);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
}
|
||||
|
||||
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
patient.setActive(true);
|
||||
builder
|
||||
.addTransactionUpdateEntry(patient)
|
||||
.conditional("Patient?active=false");
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patient.getId()); // versioned
|
||||
builder.addTransactionCreateEntry(observation);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("3", patientId.getVersionIdPart());
|
||||
assertEquals("1", observationId.getVersionIdPart());
|
||||
|
||||
// Make sure we're not introducing any extra DB operations
|
||||
assertEquals(4, myCaptureQueriesListener.logSelectQueries().size());
|
||||
|
||||
// Read back and verify that reference is now versioned
|
||||
observation = myObservationDao.read(observationId);
|
||||
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchAndIncludeVersionedReference_Asynchronous() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||
|
||||
// Create the patient
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||
myPatientDao.create(p);
|
||||
|
||||
// Update the patient
|
||||
p.getIdentifier().get(0).setValue("2");
|
||||
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Search - Non Synchronous for *
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(IBaseResource.INCLUDE_ALL));
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
// Search - Non Synchronous for named include
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(Observation.INCLUDE_PATIENT));
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAndIncludeVersionedReference_Synchronous() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||
|
||||
// Create the patient
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||
myPatientDao.create(p);
|
||||
|
||||
// Update the patient
|
||||
p.getIdentifier().get(0).setValue("2");
|
||||
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Search - Non Synchronous for *
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(IBaseResource.INCLUDE_ALL));
|
||||
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
// Search - Non Synchronous for named include
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(Observation.INCLUDE_PATIENT));
|
||||
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchAndIncludeUnersionedReference_Asynchronous() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||
|
||||
// Create the patient
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||
myPatientDao.create(p);
|
||||
|
||||
// Update the patient
|
||||
p.getIdentifier().get(0).setValue("2");
|
||||
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Search - Non Synchronous for *
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(IBaseResource.INCLUDE_ALL));
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
// Search - Non Synchronous for named include
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(Observation.INCLUDE_PATIENT));
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAndIncludeUnversionedReference_Synchronous() {
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||
|
||||
// Create the patient
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||
myPatientDao.create(p);
|
||||
|
||||
// Update the patient
|
||||
p.getIdentifier().get(0).setValue("2");
|
||||
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||
|
||||
// Search - Non Synchronous for *
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(IBaseResource.INCLUDE_ALL));
|
||||
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
// Search - Non Synchronous for named include
|
||||
{
|
||||
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(Observation.INCLUDE_PATIENT));
|
||||
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||
assertEquals(2, resources.size());
|
||||
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -257,7 +257,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
client.create().resource(resBody).execute().getId();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -468,7 +468,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
client.create().resource(resBody).execute().getId();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -798,7 +798,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
client.create().resource(resBody).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,9 @@ public class MdmQueueConsumerLoader {
|
|||
public void startListeningToMdmChannel() {
|
||||
if (myMdmChannel == null) {
|
||||
ChannelConsumerSettings config = new ChannelConsumerSettings();
|
||||
|
||||
config.setConcurrentConsumers(myMdmSettings.getConcurrentConsumers());
|
||||
|
||||
myMdmChannel = myChannelFactory.getOrCreateReceiver(IMdmSettings.EMPI_CHANNEL_NAME, ResourceModifiedJsonMessage.class, config);
|
||||
if (myMdmChannel == null) {
|
||||
ourLog.error("Unable to create receiver for {}", IMdmSettings.EMPI_CHANNEL_NAME);
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.mdm.config;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.MdmSurvivorshipSvcImpl;
|
||||
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmExpungeSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
|
||||
|
@ -29,6 +30,7 @@ import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
|
|||
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
|
||||
import ca.uhn.fhir.mdm.provider.MdmProviderLoader;
|
||||
|
@ -77,6 +79,9 @@ public class MdmConsumerConfig {
|
|||
return new MdmStorageInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
IMdmSurvivorshipService mdmSurvivorshipService() { return new MdmSurvivorshipSvcImpl(); }
|
||||
|
||||
@Bean
|
||||
MdmQueueConsumerLoader mdmQueueConsumerLoader() {
|
||||
return new MdmQueueConsumerLoader();
|
||||
|
|
|
@ -20,17 +20,18 @@ package ca.uhn.fhir.jpa.mdm.svc;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
|
||||
import ca.uhn.fhir.mdm.util.TerserUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -60,13 +61,26 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public IAnyResource mergeGoldenResources(IAnyResource theFromGoldenResource, IAnyResource theToGoldenResource, MdmTransactionContext theMdmTransactionContext) {
|
||||
public IAnyResource mergeGoldenResources(IAnyResource theFromGoldenResource, IAnyResource theMergedResource, IAnyResource theToGoldenResource, MdmTransactionContext theMdmTransactionContext) {
|
||||
Long fromGoldenResourcePid = myIdHelperService.getPidOrThrowException(theFromGoldenResource);
|
||||
Long toGoldenResourcePid = myIdHelperService.getPidOrThrowException(theToGoldenResource);
|
||||
String resourceType = theMdmTransactionContext.getResourceType();
|
||||
|
||||
//Merge attributes, to be determined when survivorship is solved.
|
||||
myGoldenResourceHelper.mergeFields(theFromGoldenResource, theToGoldenResource);
|
||||
if (theMergedResource != null ) {
|
||||
if (myGoldenResourceHelper.hasIdentifier(theMergedResource)) {
|
||||
throw new IllegalArgumentException("Manually merged resource can not contain identifiers");
|
||||
}
|
||||
myGoldenResourceHelper.mergeIndentifierFields(theFromGoldenResource, theMergedResource, theMdmTransactionContext);
|
||||
myGoldenResourceHelper.mergeIndentifierFields(theToGoldenResource, theMergedResource, theMdmTransactionContext);
|
||||
|
||||
theMergedResource.setId(theToGoldenResource.getId());
|
||||
theToGoldenResource = (IAnyResource) myMdmResourceDaoSvc.upsertGoldenResource(theMergedResource, resourceType).getResource();
|
||||
} else {
|
||||
myGoldenResourceHelper.mergeIndentifierFields(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
|
||||
myGoldenResourceHelper.mergeNonIdentiferFields(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
|
||||
//Save changes to the golden resource
|
||||
myMdmResourceDaoSvc.upsertGoldenResource(theToGoldenResource, resourceType);
|
||||
}
|
||||
|
||||
//Merge the links from the FROM to the TO resource. Clean up dangling links.
|
||||
mergeGoldenResourceLinks(theFromGoldenResource, theToGoldenResource, toGoldenResourcePid, theMdmTransactionContext);
|
||||
|
@ -83,7 +97,8 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
//Save the deprecated resource.
|
||||
myMdmResourceDaoSvc.upsertGoldenResource(theFromGoldenResource, resourceType);
|
||||
|
||||
log(theMdmTransactionContext, "Merged " + theFromGoldenResource.getIdElement().toVersionless() + " into " + theToGoldenResource.getIdElement().toVersionless());
|
||||
log(theMdmTransactionContext, "Merged " + theFromGoldenResource.getIdElement().toVersionless()
|
||||
+ " into " + theToGoldenResource.getIdElement().toVersionless());
|
||||
return theToGoldenResource;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
@Service
|
||||
public class MdmControllerSvcImpl implements IMdmControllerSvc {
|
||||
|
||||
@Autowired
|
||||
MdmControllerHelper myMdmControllerHelper;
|
||||
@Autowired
|
||||
|
@ -54,14 +55,14 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
|
|||
IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc;
|
||||
|
||||
@Override
|
||||
public IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, MdmTransactionContext theMdmTransactionContext) {
|
||||
public IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, IAnyResource theManuallyMergedGoldenResource, MdmTransactionContext theMdmTransactionContext) {
|
||||
IAnyResource fromGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId);
|
||||
IAnyResource toGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToGoldenResourceId);
|
||||
myMdmControllerHelper.validateMergeResources(fromGoldenResource, toGoldenResource);
|
||||
myMdmControllerHelper.validateSameVersion(fromGoldenResource, theFromGoldenResourceId);
|
||||
myMdmControllerHelper.validateSameVersion(toGoldenResource, theToGoldenResourceId);
|
||||
|
||||
return myGoldenResourceMergerSvc.mergeGoldenResources(fromGoldenResource, toGoldenResource, theMdmTransactionContext);
|
||||
return myGoldenResourceMergerSvc.mergeGoldenResources(fromGoldenResource, theManuallyMergedGoldenResource, toGoldenResource, theMdmTransactionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,19 +20,20 @@ package ca.uhn.fhir.jpa.mdm.svc;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.model.CanonicalEID;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.mdm.util.EIDHelper;
|
||||
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -61,23 +62,28 @@ public class MdmEidUpdateService {
|
|||
private MdmLinkDaoSvc myMdmLinkDaoSvc;
|
||||
@Autowired
|
||||
private IMdmSettings myMdmSettings;
|
||||
@Autowired
|
||||
private IMdmSurvivorshipService myMdmSurvivorshipService;
|
||||
|
||||
void handleMdmUpdate(IAnyResource theTargetResource, MatchedGoldenResourceCandidate theMatchedGoldenResourceCandidate, MdmTransactionContext theMdmTransactionContext) {
|
||||
MdmUpdateContext updateContext = new MdmUpdateContext(theMatchedGoldenResourceCandidate, theTargetResource);
|
||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(theTargetResource, updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||
|
||||
void handleMdmUpdate(IAnyResource theResource, MatchedGoldenResourceCandidate theMatchedGoldenResourceCandidate, MdmTransactionContext theMdmTransactionContext) {
|
||||
MdmUpdateContext updateContext = new MdmUpdateContext(theMatchedGoldenResourceCandidate, theResource);
|
||||
if (updateContext.isRemainsMatchedToSameGoldenResource()) {
|
||||
// Copy over any new external EIDs which don't already exist.
|
||||
// TODO NG - Eventually this call will use terser to clone data in, once the surviorship rules for copying data will be confirmed
|
||||
// myPersonHelper.updatePersonFromUpdatedEmpiTarget(updateContext.getMatchedPerson(), theResource, theEmpiTransactionContext);
|
||||
if (!updateContext.isIncomingResourceHasAnEid() || updateContext.isHasEidsInCommon()) {
|
||||
//update to patient that uses internal EIDs only.
|
||||
myMdmLinkSvc.updateLink(updateContext.getMatchedGoldenResource(), theResource, theMatchedGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
myMdmLinkSvc.updateLink(updateContext.getMatchedGoldenResource(), theTargetResource, theMatchedGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
} else if (!updateContext.isHasEidsInCommon()) {
|
||||
handleNoEidsInCommon(theResource, theMatchedGoldenResourceCandidate, theMdmTransactionContext, updateContext);
|
||||
handleNoEidsInCommon(theTargetResource, theMatchedGoldenResourceCandidate, theMdmTransactionContext, updateContext);
|
||||
}
|
||||
} else {
|
||||
//This is a new linking scenario. we have to break the existing link and link to the new Golden Resource. For now, we create duplicate.
|
||||
//updated patient has an EID that matches to a new candidate. Link them, and set the Golden Resources possible duplicates
|
||||
linkToNewGoldenResourceAndFlagAsDuplicate(theResource, updateContext.getExistingGoldenResource(), updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||
linkToNewGoldenResourceAndFlagAsDuplicate(theTargetResource, updateContext.getExistingGoldenResource(), updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||
|
||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(theTargetResource, updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||
myMdmResourceDaoSvc.upsertGoldenResource(updateContext.getMatchedGoldenResource(), theMdmTransactionContext.getResourceType());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +115,7 @@ public class MdmEidUpdateService {
|
|||
|
||||
private void createNewGoldenResourceAndFlagAsDuplicate(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext, IAnyResource theOldGoldenResource) {
|
||||
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource);
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource, theMdmTransactionContext);
|
||||
|
||||
myMdmLinkSvc.updateLink(newGoldenResource, theResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
myMdmLinkSvc.updateLink(newGoldenResource, theOldGoldenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.mdm.svc;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
|
||||
|
@ -63,6 +64,8 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
|
|||
IMdmSettings myMdmSettings;
|
||||
@Autowired
|
||||
MessageHelper myMessageHelper;
|
||||
@Autowired
|
||||
IMdmSurvivorshipService myMdmSurvivorshipService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
|
@ -89,6 +92,12 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
|
|||
mdmLink.setMatchResult(theMatchResult);
|
||||
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
|
||||
myMdmLinkDaoSvc.save(mdmLink);
|
||||
|
||||
if (theMatchResult == MdmMatchResultEnum.MATCH) {
|
||||
// only apply survivorship rules in case of a match
|
||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(theSourceResource, theGoldenResource, theMdmContext);
|
||||
}
|
||||
|
||||
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmContext.getResourceType());
|
||||
if (theMatchResult == MdmMatchResultEnum.NO_MATCH) {
|
||||
// Need to find a new Golden Resource to link this target to
|
||||
|
|
|
@ -122,7 +122,7 @@ public class MdmMatchLinkSvc {
|
|||
|
||||
private void handleMdmWithNoCandidates(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext) {
|
||||
log(theMdmTransactionContext, String.format("There were no matched candidates for MDM, creating a new %s.", theResource.getIdElement().getResourceType()));
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource);
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource, theMdmTransactionContext);
|
||||
// TODO GGG :)
|
||||
// 1. Get the right helper
|
||||
// 2. Create source resource for the MDM source
|
||||
|
@ -136,13 +136,12 @@ public class MdmMatchLinkSvc {
|
|||
|
||||
if (myGoldenResourceHelper.isPotentialDuplicate(golenResource, theSourceResource)) {
|
||||
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theSourceResource);
|
||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theSourceResource, theMdmTransactionContext);
|
||||
myMdmLinkSvc.updateLink(newGoldenResource, theSourceResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
myMdmLinkSvc.updateLink(newGoldenResource, golenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
} else {
|
||||
if (theGoldenResourceCandidate.isMatch()) {
|
||||
myGoldenResourceHelper.handleExternalEidAddition(golenResource, theSourceResource, theMdmTransactionContext);
|
||||
//TODO MDM GGG/NG: eventually we need to add survivorship rules of attributes here. Currently no data is copied over except EIDs.
|
||||
}
|
||||
myMdmLinkSvc.updateLink(golenResource, theSourceResource, theGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package ca.uhn.fhir.jpa.mdm.svc;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.mdm.util.TerserUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
|
||||
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
/**
|
||||
* Merges two golden resources by overwriting all field values on theGoldenResource param for CREATE_RESOURCE,
|
||||
* UPDATE_RESOURCE, SUBMIT_RESOURCE_TO_MDM, UPDATE_LINK (when setting to MATCH) and MANUAL_MERGE_GOLDEN_RESOURCES.
|
||||
* PID, identifiers and meta values are not affected by this operation.
|
||||
*
|
||||
* @param theTargetResource Target resource to retrieve fields from
|
||||
* @param theGoldenResource Golden resource to merge fields into
|
||||
* @param theMdmTransactionContext Current transaction context
|
||||
* @param <T>
|
||||
*/
|
||||
@Override
|
||||
public <T extends IBase> void applySurvivorshipRulesToGoldenResource(T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
|
||||
switch (theMdmTransactionContext.getRestOperation()) {
|
||||
case MERGE_GOLDEN_RESOURCES:
|
||||
TerserUtil.mergeFields(myFhirContext, (IBaseResource) theTargetResource, (IBaseResource) theGoldenResource, TerserUtil.EXCLUDE_IDS_AND_META);
|
||||
break;
|
||||
default:
|
||||
TerserUtil.replaceFields(myFhirContext, (IBaseResource) theTargetResource, (IBaseResource) theGoldenResource, TerserUtil.EXCLUDE_IDS_AND_META);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,12 +35,10 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
|
|||
protected IAnyResource myGoldenMedication;
|
||||
protected StringType myGoldenMedicationId;
|
||||
|
||||
|
||||
@Autowired
|
||||
IInterceptorService myInterceptorService;
|
||||
PointcutLatch afterMdmLatch = new PointcutLatch(Pointcut.MDM_AFTER_PERSISTED_RESOURCE_CHECKED);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
|
@ -58,7 +56,6 @@ public class MdmProviderBatchR4Test extends BaseLinkR4Test {
|
|||
myGoldenMedication = getGoldenResourceFromTargetResource(myMedication);
|
||||
myGoldenMedicationId = new StringType(myGoldenMedication.getIdElement().getValue());
|
||||
|
||||
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.MDM_AFTER_PERSISTED_RESOURCE_CHECKED, afterMdmLatch);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.entity.MdmLink;
|
|||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
|
||||
import ca.uhn.fhir.mdm.util.TerserUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
@ -38,19 +39,45 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
myToGoldenPatientId = new StringType(myToGoldenPatient.getIdElement().getValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMergeWithOverride() {
|
||||
myFromGoldenPatient.getName().clear();
|
||||
myFromGoldenPatient.addName().setFamily("Family").addGiven("Given");
|
||||
myFromGoldenPatient.addCommunication();
|
||||
myFromGoldenPatient.addExtension();
|
||||
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
|
||||
myToGoldenPatientId, myFromGoldenPatient, myRequestDetails);
|
||||
|
||||
assertEquals(myFromGoldenPatient.getName().size(), mergedSourcePatient.getName().size());
|
||||
assertEquals(myFromGoldenPatient.getName().get(0).getNameAsSingleString(), mergedSourcePatient.getName().get(0).getNameAsSingleString());
|
||||
assertEquals(myFromGoldenPatient.getCommunication().size(), mergedSourcePatient.getCommunication().size());
|
||||
assertEquals(myFromGoldenPatient.getExtension().size(), mergedSourcePatient.getExtension().size());
|
||||
|
||||
Patient toGoldenPatient = myPatientDao.read(myToGoldenPatient.getIdElement().toUnqualifiedVersionless());
|
||||
assertEquals(toGoldenPatient.getName().size(), mergedSourcePatient.getName().size());
|
||||
assertEquals(toGoldenPatient.getName().get(0).getNameAsSingleString(), mergedSourcePatient.getName().get(0).getNameAsSingleString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
|
||||
myToGoldenPatientId, myRequestDetails);
|
||||
myToGoldenPatientId, null, myRequestDetails);
|
||||
|
||||
// we do not check setActive anymore - as not all types support that
|
||||
assertTrue(MdmResourceUtil.isGoldenRecord(mergedSourcePatient));
|
||||
assertTrue(!MdmResourceUtil.isGoldenRecordRedirected(mergedSourcePatient));
|
||||
|
||||
assertTrue(MdmResourceUtil.isGoldenRecord(myFromGoldenPatient));
|
||||
assertEquals(myToGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
|
||||
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(myToGoldenPatient)));
|
||||
assertEquals(1, getAllRedirectedGoldenPatients().size());
|
||||
assertEquals(1, getAllGoldenPatients().size());
|
||||
|
||||
Patient fromSourcePatient = myPatientDao.read(myFromGoldenPatient.getIdElement().toUnqualifiedVersionless());
|
||||
assertThat(fromSourcePatient.getActive(), is(false));
|
||||
|
||||
assertTrue(!MdmResourceUtil.isGoldenRecord(fromSourcePatient));
|
||||
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(fromSourcePatient));
|
||||
|
||||
//TODO GGG eventually this will need to check a redirect... this is a hack which doesnt work
|
||||
|
@ -67,12 +94,39 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeWithManualOverride() {
|
||||
Patient patient = TerserUtil.clone(myFhirContext, myFromGoldenPatient);
|
||||
patient.setIdElement(null);
|
||||
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
|
||||
myToGoldenPatientId, patient, myRequestDetails);
|
||||
|
||||
assertEquals(myToGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
|
||||
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(myToGoldenPatient)));
|
||||
assertEquals(1, getAllRedirectedGoldenPatients().size());
|
||||
assertEquals(1, getAllGoldenPatients().size());
|
||||
|
||||
Patient fromSourcePatient = myPatientDao.read(myFromGoldenPatient.getIdElement().toUnqualifiedVersionless());
|
||||
assertTrue(!MdmResourceUtil.isGoldenRecord(fromSourcePatient));
|
||||
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(fromSourcePatient));
|
||||
|
||||
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(myFromGoldenPatient);
|
||||
assertThat(links, hasSize(1));
|
||||
|
||||
MdmLink link = links.get(0);
|
||||
assertEquals(link.getSourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
|
||||
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmanagedMerge() {
|
||||
StringType fromGoldenResourceId = new StringType(createPatient().getIdElement().getValue());
|
||||
StringType toGoldenResourceId = new StringType(createPatient().getIdElement().getValue());
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(fromGoldenResourceId, toGoldenResourceId, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(fromGoldenResourceId, toGoldenResourceId, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Only MDM managed resources can be merged. MDM managed resources must have the HAPI-MDM tag.", e.getMessage());
|
||||
|
@ -82,19 +136,19 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
@Test
|
||||
public void testNullParams() {
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(null, null, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(null, null, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("fromGoldenResourceId cannot be null", e.getMessage());
|
||||
}
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(null, myToGoldenPatientId, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(null, myToGoldenPatientId, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("fromGoldenResourceId cannot be null", e.getMessage());
|
||||
}
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(myFromGoldenPatientId, null, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(myFromGoldenPatientId, null, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("toGoldenResourceId cannot be null", e.getMessage());
|
||||
|
@ -104,28 +158,35 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
@Test
|
||||
public void testBadParams() {
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Patient/1"), new StringType("Patient/1"), myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Patient/1"), new StringType("Patient/1"), null, myRequestDetails);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("fromGoldenResourceId must be different from toGoldenResourceId", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Patient/abc"), myToGoldenPatientId, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Patient/abc"), myToGoldenPatientId, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertEquals("Resource Patient/abc is not known", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Organization/abc"), myToGoldenPatientId, myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Patient/abc"), myToGoldenPatientId, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertEquals("Resource Patient/abc is not known", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(new StringType("Organization/abc"), myToGoldenPatientId, null, myRequestDetails);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertEquals("Resource Organization/abc is not known", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
myMdmProvider.mergeGoldenResources(myFromGoldenPatientId, new StringType("Patient/abc"), myRequestDetails);
|
||||
myMdmProvider.mergeGoldenResources(myFromGoldenPatientId, new StringType("Patient/abc"), null, myRequestDetails);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertEquals("Resource Patient/abc is not known", e.getMessage());
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package ca.uhn.fhir.jpa.mdm.provider;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.mdm.api.MdmConstants;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.util.MessageHelper;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
@ -15,8 +15,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.hl7.fhir.r4.model.DateType;
|
|||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Identifier;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -34,8 +35,7 @@ import java.util.stream.Collectors;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
|
||||
private Patient mergeGoldenPatients() {
|
||||
assertEquals(0, redirectLinkCount());
|
||||
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(myFromGoldenPatient, myToGoldenPatient, createMdmContext());
|
||||
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(myFromGoldenPatient, null, myToGoldenPatient, createMdmContext());
|
||||
assertEquals(1, redirectLinkCount());
|
||||
return retval;
|
||||
}
|
||||
|
@ -151,25 +151,44 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
populatePatient(myFromGoldenPatient);
|
||||
|
||||
Patient mergedSourcePatient = mergeGoldenPatients();
|
||||
// TODO NG - Revisit when rules are ready
|
||||
// HumanName returnedName = mergedSourcePatient.getNameFirstRep();
|
||||
// assertEquals(GIVEN_NAME, returnedName.getGivenAsSingleString());
|
||||
// assertEquals(FAMILY_NAME, returnedName.getFamily());
|
||||
// assertEquals(POSTAL_CODE, mergedSourcePatient.getAddressFirstRep().getPostalCode());
|
||||
HumanName returnedName = mergedSourcePatient.getNameFirstRep();
|
||||
assertEquals(GIVEN_NAME, returnedName.getGivenAsSingleString());
|
||||
assertEquals(FAMILY_NAME, returnedName.getFamily());
|
||||
assertEquals(POSTAL_CODE, mergedSourcePatient.getAddressFirstRep().getPostalCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyFromFullTo() {
|
||||
myFromGoldenPatient.getName().add(new HumanName().addGiven(BAD_GIVEN_NAME));
|
||||
populatePatient(myToGoldenPatient);
|
||||
print(myFromGoldenPatient);
|
||||
|
||||
Patient mergedSourcePatient = mergeGoldenPatients();
|
||||
print(mergedSourcePatient);
|
||||
HumanName returnedName = mergedSourcePatient.getNameFirstRep();
|
||||
assertEquals(GIVEN_NAME, returnedName.getGivenAsSingleString());
|
||||
assertEquals(FAMILY_NAME, returnedName.getFamily());
|
||||
assertEquals(POSTAL_CODE, mergedSourcePatient.getAddressFirstRep().getPostalCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualOverride() {
|
||||
Patient manuallyMergedPatient = new Patient();
|
||||
populatePatient(manuallyMergedPatient);
|
||||
manuallyMergedPatient.getNameFirstRep().setFamily("TestFamily");
|
||||
manuallyMergedPatient.getNameFirstRep().getGiven().clear();
|
||||
manuallyMergedPatient.getNameFirstRep().addGiven("TestGiven");
|
||||
|
||||
MdmTransactionContext ctx = createMdmContext();
|
||||
ctx.setRestOperation(MdmTransactionContext.OperationType.MANUAL_MERGE_GOLDEN_RESOURCES);
|
||||
Patient mergedSourcePatient = (Patient) myGoldenResourceMergerSvc
|
||||
.mergeGoldenResources(myFromGoldenPatient, manuallyMergedPatient, myToGoldenPatient, ctx);
|
||||
|
||||
HumanName returnedName = mergedSourcePatient.getNameFirstRep();
|
||||
assertEquals("TestGiven TestFamily", returnedName.getNameAsSingleString());
|
||||
assertEquals(POSTAL_CODE, mergedSourcePatient.getAddressFirstRep().getPostalCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromLinkToNoLink() {
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
|
@ -376,39 +395,42 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void testMergeNames() {
|
||||
// TODO NG - Revisit when rules are available
|
||||
// myFromSourcePatient.addName().addGiven("Jim");
|
||||
// myFromSourcePatient.getNameFirstRep().addGiven("George");
|
||||
// assertThat(myFromSourcePatient.getName(), hasSize(1));
|
||||
// assertThat(myFromSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
//
|
||||
// myToSourcePatient.addName().addGiven("Jeff");
|
||||
// myToSourcePatient.getNameFirstRep().addGiven("George");
|
||||
// assertThat(myToSourcePatient.getName(), hasSize(1));
|
||||
// assertThat(myToSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
//
|
||||
// Patient mergedSourcePatient = mergeSourcePatients();
|
||||
// assertThat(mergedSourcePatient.getName(), hasSize(2));
|
||||
// assertThat(mergedSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
// assertThat(mergedSourcePatient.getName().get(1).getGiven(), hasSize(2));
|
||||
myFromGoldenPatient.addName().addGiven("Jim");
|
||||
myFromGoldenPatient.getNameFirstRep().addGiven("George");
|
||||
assertThat(myFromGoldenPatient.getName(), hasSize(1));
|
||||
assertThat(myFromGoldenPatient.getName().get(0).getGiven(), hasSize(2));
|
||||
|
||||
myToGoldenPatient.addName().addGiven("Jeff");
|
||||
myToGoldenPatient.getNameFirstRep().addGiven("George");
|
||||
assertThat(myToGoldenPatient.getName(), hasSize(1));
|
||||
assertThat(myToGoldenPatient.getName().get(0).getGiven(), hasSize(2));
|
||||
|
||||
Patient mergedSourcePatient = mergeGoldenPatients();
|
||||
assertThat(mergedSourcePatient.getName(), hasSize(2));
|
||||
assertThat(mergedSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
assertThat(mergedSourcePatient.getName().get(1).getGiven(), hasSize(2));
|
||||
|
||||
assertThat(mergedSourcePatient.getName().get(0).getNameAsSingleString(), is("Jeff George"));
|
||||
assertThat(mergedSourcePatient.getName().get(1).getNameAsSingleString(), is("Jim George"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeNamesAllSame() {
|
||||
// TODO NG - Revisit when rules are available
|
||||
// myFromSourcePatient.addName().addGiven("Jim");
|
||||
// myFromSourcePatient.getNameFirstRep().addGiven("George");
|
||||
// assertThat(myFromSourcePatient.getName(), hasSize(1));
|
||||
// assertThat(myFromSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
//
|
||||
// myToSourcePatient.addName().addGiven("Jim");
|
||||
// myToSourcePatient.getNameFirstRep().addGiven("George");
|
||||
// assertThat(myToSourcePatient.getName(), hasSize(1));
|
||||
// assertThat(myToSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
//
|
||||
// mergeSourcePatients();
|
||||
// assertThat(myToSourcePatient.getName(), hasSize(1));
|
||||
// assertThat(myToSourcePatient.getName().get(0).getGiven(), hasSize(2));
|
||||
myFromGoldenPatient.addName().addGiven("Jim");
|
||||
myFromGoldenPatient.getNameFirstRep().addGiven("George");
|
||||
assertThat(myFromGoldenPatient.getName(), hasSize(1));
|
||||
assertThat(myFromGoldenPatient.getName().get(0).getGiven(), hasSize(2));
|
||||
|
||||
myToGoldenPatient.addName().addGiven("Jim");
|
||||
myToGoldenPatient.getNameFirstRep().addGiven("George");
|
||||
assertThat(myToGoldenPatient.getName(), hasSize(1));
|
||||
assertThat(myToGoldenPatient.getName().get(0).getGiven(), hasSize(2));
|
||||
|
||||
mergeGoldenPatients();
|
||||
assertThat(myToGoldenPatient.getName(), hasSize(1));
|
||||
assertThat(myToGoldenPatient.getName().get(0).getGiven(), hasSize(2));
|
||||
|
||||
assertThat(myToGoldenPatient.getName().get(0).getNameAsSingleString(), is("Jim George"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -425,11 +447,14 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
mergeGoldenPatients();
|
||||
|
||||
assertThat(myToGoldenPatient.getIdentifier(), hasSize(4));
|
||||
|
||||
assertTrue(myToGoldenPatient.getIdentifier().get(0).equalsDeep(new Identifier().setValue("aaa").setSystem("SYSTEM1")));
|
||||
assertTrue(myToGoldenPatient.getIdentifier().get(1).equalsDeep(new Identifier().setValue("ccc").setSystem("SYSTEM1")));
|
||||
assertTrue(myToGoldenPatient.getIdentifier().get(2).equalsDeep(new Identifier().setValue("bbb").setSystem("SYSTEM1")));
|
||||
assertTrue(myToGoldenPatient.getIdentifier().get(3).equalsDeep(new Identifier().setValue("ccc").setSystem("SYSTEM2")));
|
||||
}
|
||||
|
||||
private MdmLink createMdmLink(Patient theSourcePatient, Patient theTargetPatient) {
|
||||
//TODO GGG Ensure theis comment can be safely removed
|
||||
//theSourcePatient.addLink().setTarget(new Reference(theTargetPatient));
|
||||
return myMdmLinkDaoSvc.createOrUpdateLinkEntity(theSourcePatient, theTargetPatient, POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
|
||||
}
|
||||
|
||||
|
|
|
@ -51,23 +51,16 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
|
|||
assertLinkCount(0);
|
||||
Patient goldenPatient = createGoldenPatient();
|
||||
IdType sourcePatientId = goldenPatient.getIdElement().toUnqualifiedVersionless();
|
||||
// TODO NG should be ok to remove - assertEquals(0, goldenPatient.getLink().size());
|
||||
Patient patient = createPatient();
|
||||
|
||||
{
|
||||
myMdmLinkSvc.updateLink(goldenPatient, patient, POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
|
||||
assertLinkCount(1);
|
||||
// TODO NG should be ok to remove
|
||||
// Patient newSourcePatient = myPatientDao.read(sourcePatientId);
|
||||
// assertEquals(1, newSourcePatient.getLink().size());
|
||||
}
|
||||
|
||||
{
|
||||
myMdmLinkSvc.updateLink(goldenPatient, patient, MdmMatchOutcome.NO_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
|
||||
assertLinkCount(1);
|
||||
// TODO NG should be ok to remove
|
||||
// Patient newSourcePatient = myPatientDao.read(sourcePatientId);
|
||||
// assertEquals(0, newSourcePatient.getLink().size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +122,6 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void testManualMdmLinksCannotBeModifiedBySystem() {
|
||||
// Patient goldenPatient = createGoldenPatient(buildJaneSourcePatient());
|
||||
Patient goldenPatient = createGoldenPatient(buildJanePatient());
|
||||
Patient patient = createPatient(buildJanePatient());
|
||||
|
||||
|
@ -144,7 +136,6 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void testAutomaticallyAddedNO_MATCHMdmLinksAreNotAllowed() {
|
||||
// Patient goldenPatient = createGoldenPatient(buildJaneSourcePatient());
|
||||
Patient goldenPatient = createGoldenPatient(buildJanePatient());
|
||||
Patient patient = createPatient(buildJanePatient());
|
||||
|
||||
|
@ -159,7 +150,6 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void testSyncDoesNotSyncNoMatchLinks() {
|
||||
// Patient sourcePatient = createGoldenPatient(buildJaneSourcePatient());
|
||||
Patient goldenPatient = createGoldenPatient(buildJanePatient());
|
||||
Patient patient1 = createPatient(buildJanePatient());
|
||||
Patient patient2 = createPatient(buildJanePatient());
|
||||
|
@ -171,7 +161,6 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
|
|||
List<MdmLink> targets = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(goldenPatient);
|
||||
assertFalse(targets.isEmpty());
|
||||
assertEquals(2, targets.size());
|
||||
// TODO NG - OK? original assertTrue(goldenPatient.hasLink());
|
||||
|
||||
//TODO GGG update this test once we decide what has to happen here. There is no more "syncing links"
|
||||
//assertEquals(patient1.getIdElement().toVersionless().getValue(), sourcePatient.getLinkFirstRep().getTarget().getReference());
|
||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.mdm.api.MdmConstants;
|
|||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.model.CanonicalEID;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.mdm.util.EIDHelper;
|
||||
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
|
||||
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
|
||||
|
@ -34,12 +35,7 @@ import static ca.uhn.fhir.mdm.api.MdmMatchResultEnum.NO_MATCH;
|
|||
import static ca.uhn.fhir.mdm.api.MdmMatchResultEnum.POSSIBLE_DUPLICATE;
|
||||
import static ca.uhn.fhir.mdm.api.MdmMatchResultEnum.POSSIBLE_MATCH;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.blankOrNullString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -183,13 +179,13 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
Optional<MdmLink> mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(patient.getIdElement().getIdPartAsLong());
|
||||
Patient read = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
|
||||
|
||||
// TODO NG - rules haven't been determined yet revisit once implemented...
|
||||
// assertThat(read.getNameFirstRep().getFamily(), is(equalTo(patient.getNameFirstRep().getFamily())));
|
||||
// assertThat(read.getNameFirstRep().getGivenAsSingleString(), is(equalTo(patient.getNameFirstRep().getGivenAsSingleString())));
|
||||
// assertThat(read.getBirthDateElement().toHumanDisplay(), is(equalTo(patient.getBirthDateElement().toHumanDisplay())));
|
||||
// assertThat(read.getTelecomFirstRep().getValue(), is(equalTo(patient.getTelecomFirstRep().getValue())));
|
||||
// assertThat(read.getPhoto().getData(), is(equalTo(patient.getPhotoFirstRep().getData())));
|
||||
// assertThat(read.getGender(), is(equalTo(patient.getGender())));
|
||||
assertThat(read.getNameFirstRep().getFamily(), is(equalTo(patient.getNameFirstRep().getFamily())));
|
||||
assertThat(read.getNameFirstRep().getGivenAsSingleString(), is(equalTo(patient.getNameFirstRep().getGivenAsSingleString())));
|
||||
assertThat(read.getBirthDateElement().toHumanDisplay(), is(equalTo(patient.getBirthDateElement().toHumanDisplay())));
|
||||
assertThat(read.getTelecomFirstRep().getValue(), is(equalTo(patient.getTelecomFirstRep().getValue())));
|
||||
assertThat(read.getPhoto().size(), is(equalTo(patient.getPhoto().size())));
|
||||
assertThat(read.getPhotoFirstRep().getData(), is(equalTo(patient.getPhotoFirstRep().getData())));
|
||||
assertThat(read.getGender(), is(equalTo(patient.getGender())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -245,7 +241,6 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void testHavingMultipleEIDsOnIncomingPatientMatchesCorrectly() {
|
||||
|
||||
Patient patient1 = buildJanePatient();
|
||||
addExternalEID(patient1, "id_1");
|
||||
addExternalEID(patient1, "id_2");
|
||||
|
@ -273,7 +268,6 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
List<MdmLink> possibleDuplicates = myMdmLinkDaoSvc.getPossibleDuplicates();
|
||||
assertThat(possibleDuplicates, hasSize(1));
|
||||
|
||||
|
||||
List<Long> duplicatePids = Stream.of(patient1, patient2)
|
||||
.map(this::getGoldenResourceFromTargetResource)
|
||||
.map(myIdHelperService::getPidOrNull)
|
||||
|
@ -352,7 +346,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
|
||||
//own individual GoldenResource for the purpose of this test.
|
||||
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient2);
|
||||
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient2, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
|
||||
myMdmLinkSvc.updateLink(goldenResource, janePatient2, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
|
||||
assertThat(janePatient, is(not(sameGoldenResourceAs(janePatient2))));
|
||||
|
||||
|
@ -443,7 +437,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
public void testCreateGoldenResourceFromMdmTarget() {
|
||||
// Create Use Case #2 - adding patient with no EID
|
||||
Patient janePatient = buildJanePatient();
|
||||
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient);
|
||||
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
|
||||
|
||||
// golden record now contains HAPI-generated EID and HAPI tag
|
||||
assertTrue(MdmResourceUtil.isMdmManaged(janeGoldenResourcePatient));
|
||||
|
@ -479,8 +473,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
Patient sourcePatientFromTarget = (Patient) getGoldenResourceFromTargetResource(janePaulPatient);
|
||||
HumanName nameFirstRep = sourcePatientFromTarget.getNameFirstRep();
|
||||
|
||||
// TODO NG attribute propagation has been removed - revisit once source survivorship rules are defined
|
||||
// assertThat(nameFirstRep.getGivenAsSingleString(), is(equalToIgnoringCase("paul")));
|
||||
assertThat(nameFirstRep.getGivenAsSingleString(), is(equalToIgnoringCase("paul")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -491,8 +484,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
Patient sourcePatientFromTarget = (Patient) getGoldenResourceFromTargetResource(paul);
|
||||
|
||||
// TODO NG - rules haven't been determined yet revisit once implemented...
|
||||
// assertThat(sourcePatientFromTarget.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE)));
|
||||
assertThat(sourcePatientFromTarget.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE)));
|
||||
|
||||
Patient paul2 = buildPaulPatient();
|
||||
paul2.setGender(Enumerations.AdministrativeGender.FEMALE);
|
||||
|
@ -503,7 +495,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
//Newly matched patients aren't allowed to overwrite GoldenResource Attributes unless they are empty,
|
||||
// so gender should still be set to male.
|
||||
Patient paul2GoldenResource = (Patient) getGoldenResourceFromTargetResource(paul2);
|
||||
// assertThat(paul2GoldenResource.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE)));
|
||||
assertThat(paul2GoldenResource.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -515,8 +507,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
paul = createPatientAndUpdateLinks(paul);
|
||||
|
||||
Patient sourcePatientFromTarget = (Patient) getGoldenResourceFromTargetResource(paul);
|
||||
// TODO NG - rules haven't been determined yet revisit once implemented...
|
||||
// assertThat(sourcePatientFromTarget.getBirthDateElement().getValueAsString(), is(incorrectBirthdate));
|
||||
assertThat(sourcePatientFromTarget.getBirthDateElement().getValueAsString(), is(incorrectBirthdate));
|
||||
|
||||
String correctBirthdate = "1990-06-28";
|
||||
paul.getBirthDateElement().setValueAsString(correctBirthdate);
|
||||
|
@ -524,8 +515,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
paul = updatePatientAndUpdateLinks(paul);
|
||||
|
||||
sourcePatientFromTarget = (Patient) getGoldenResourceFromTargetResource(paul);
|
||||
// TODO NG - rules haven't been determined yet revisit once implemented...
|
||||
// assertThat(sourcePatientFromTarget.getBirthDateElement().getValueAsString(), is(equalTo(correctBirthdate)));
|
||||
assertThat(sourcePatientFromTarget.getBirthDateElement().getValueAsString(), is(equalTo(correctBirthdate)));
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
||||
|
@ -612,6 +602,5 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
List<MdmLink> possibleDuplicates = myMdmLinkDaoSvc.getPossibleDuplicates();
|
||||
assertThat(possibleDuplicates, hasSize(1));
|
||||
assertThat(patient3, is(possibleDuplicateOf(patient1)));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package ca.uhn.fhir.jpa.mdm.svc;
|
||||
|
||||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MdmSurvivorshipSvcImplTest extends BaseMdmR4Test {
|
||||
|
||||
@Autowired
|
||||
private IMdmSurvivorshipService myMdmSurvivorshipService;
|
||||
|
||||
@Test
|
||||
public void testRulesOnCreate() {
|
||||
Patient p1 = buildFrankPatient();
|
||||
Patient p2 = new Patient();
|
||||
|
||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
|
||||
|
||||
assertFalse(p2.hasIdElement());
|
||||
assertTrue(p2.getIdentifier().isEmpty());
|
||||
assertTrue(p2.getMeta().isEmpty());
|
||||
|
||||
assertTrue(p1.getNameFirstRep().equalsDeep(p2.getNameFirstRep()));
|
||||
assertNull(p2.getBirthDate());
|
||||
assertEquals(p1.getTelecom().size(), p2.getTelecom().size());
|
||||
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRulesOnMerge() {
|
||||
Patient p1 = buildFrankPatient();
|
||||
String p1Name = p1.getNameFirstRep().getNameAsSingleString();
|
||||
Patient p2 = buildPaulPatient();
|
||||
String p2Name = p2.getNameFirstRep().getNameAsSingleString();
|
||||
|
||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.MERGE_GOLDEN_RESOURCES));
|
||||
|
||||
assertFalse(p2.hasIdElement());
|
||||
assertFalse(p2.getIdentifier().isEmpty());
|
||||
assertTrue(p2.getMeta().isEmpty());
|
||||
|
||||
assertEquals(2, p2.getName().size());
|
||||
assertEquals(p2Name, p2.getName().get(0).getNameAsSingleString());
|
||||
assertEquals(p1Name, p2.getName().get(1).getNameAsSingleString());
|
||||
assertNull(p2.getBirthDate());
|
||||
|
||||
assertEquals(p1.getTelecom().size(), p1.getTelecom().size());
|
||||
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
|
||||
}
|
||||
|
||||
}
|
|
@ -123,6 +123,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
|||
Builder.BuilderWithTableName quantityTable = version.onTable("HFJ_SPIDX_QUANTITY");
|
||||
quantityTable.modifyColumn("20210116.1", "SP_VALUE").nullable().failureAllowed().withType(ColumnTypeEnum.DOUBLE);
|
||||
|
||||
// HFJ_RES_LINK
|
||||
version.onTable("HFJ_RES_LINK")
|
||||
.addColumn("20210126.1", "TARGET_RESOURCE_VERSION").nullable().type(ColumnTypeEnum.LONG);
|
||||
}
|
||||
|
||||
protected void init520() {
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.api;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.config;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
@ -20,20 +20,25 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.dstu2.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
// TODO: move this to ca.uhn.fhir.jpa.model.config
|
||||
public class ModelConfig {
|
||||
|
||||
|
@ -90,6 +95,9 @@ public class ModelConfig {
|
|||
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
|
||||
|
||||
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
|
||||
private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
|
||||
private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
|
||||
private boolean myRespectVersionsForSearchIncludes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -268,7 +276,7 @@ public class ModelConfig {
|
|||
}
|
||||
|
||||
HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
|
||||
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
|
||||
for (String next : defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
|
||||
while (next.endsWith("/")) {
|
||||
next = next.substring(0, next.length() - 1);
|
||||
}
|
||||
|
@ -618,6 +626,110 @@ public class ModelConfig {
|
|||
myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public Set<String> getAutoVersionReferenceAtPaths() {
|
||||
return myAutoVersionReferenceAtPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||
* <p>
|
||||
* Versions will only be added if the reference does not already have a version, so any versioned references
|
||||
* supplied by the client will take precedence over the automatic current version.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that for this setting to be useful, the {@link ParserOptions}
|
||||
* {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
|
||||
* option must also be set.
|
||||
* </p>
|
||||
*
|
||||
* @param thePaths A collection of reference paths for which the versions will be appended automatically
|
||||
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
|
||||
* only resource name and field names with dots separating is allowed here (no repetition
|
||||
* indicators, FluentPath expressions, etc.)
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public void setAutoVersionReferenceAtPaths(String... thePaths) {
|
||||
Set<String> paths = Collections.emptySet();
|
||||
if (thePaths != null) {
|
||||
paths = new HashSet<>(Arrays.asList(thePaths));
|
||||
}
|
||||
setAutoVersionReferenceAtPaths(paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||
* <p>
|
||||
* Versions will only be added if the reference does not already have a version, so any versioned references
|
||||
* supplied by the client will take precedence over the automatic current version.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that for this setting to be useful, the {@link ParserOptions}
|
||||
* {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
|
||||
* option must also be set
|
||||
* </p>
|
||||
*
|
||||
* @param thePaths A collection of reference paths for which the versions will be appended automatically
|
||||
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
|
||||
* only resource name and field names with dots separating is allowed here (no repetition
|
||||
* indicators, FluentPath expressions, etc.)
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public void setAutoVersionReferenceAtPaths(Set<String> thePaths) {
|
||||
Set<String> paths = defaultIfNull(thePaths, Collections.emptySet());
|
||||
Map<String, Set<String>> byType = new HashMap<>();
|
||||
for (String nextPath : paths) {
|
||||
int doxIdx = nextPath.indexOf('.');
|
||||
Validate.isTrue(doxIdx > 0, "Invalid path for auto-version reference at path: %s", nextPath);
|
||||
String type = nextPath.substring(0, doxIdx);
|
||||
byType.computeIfAbsent(type, t -> new HashSet<>()).add(nextPath);
|
||||
}
|
||||
|
||||
|
||||
myAutoVersionReferenceAtPaths = paths;
|
||||
myTypeToAutoVersionReferenceAtPaths = byType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sub-collection of {@link #getAutoVersionReferenceAtPaths()} containing only paths
|
||||
* for the given resource type.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public Set<String> getAutoVersionReferenceAtPathsByResourceType(String theResourceType) {
|
||||
Validate.notEmpty(theResourceType, "theResourceType must not be null or empty");
|
||||
Set<String> retVal = myTypeToAutoVersionReferenceAtPaths.get(theResourceType);
|
||||
retVal = defaultIfNull(retVal, Collections.emptySet());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
|
||||
* This may have performance impacts on heavily loaded systems.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public boolean isRespectVersionsForSearchIncludes() {
|
||||
return myRespectVersionsForSearchIncludes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
|
||||
* This may have performance impacts on heavily loaded systems.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public void setRespectVersionsForSearchIncludes(boolean theRespectVersionsForSearchIncludes) {
|
||||
myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
|
||||
}
|
||||
|
||||
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
|
||||
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
@ -26,6 +26,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
|
|||
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
|
@ -88,12 +89,12 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
|
||||
@FullTextField
|
||||
private String myTargetResourceUrl;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_VERSION", nullable = true)
|
||||
private Long myTargetResourceVersion;
|
||||
@FullTextField
|
||||
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date myUpdated;
|
||||
|
||||
@Transient
|
||||
private transient String myTargetResourceId;
|
||||
|
||||
|
@ -101,6 +102,14 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
super();
|
||||
}
|
||||
|
||||
public Long getTargetResourceVersion() {
|
||||
return myTargetResourceVersion;
|
||||
}
|
||||
|
||||
public void setTargetResourceVersion(Long theTargetResourceVersion) {
|
||||
myTargetResourceVersion = theTargetResourceVersion;
|
||||
}
|
||||
|
||||
public String getTargetResourceId() {
|
||||
if (myTargetResourceId == null && myTargetResource != null) {
|
||||
myTargetResourceId = getTargetResource().getIdDt().getIdPart();
|
||||
|
@ -178,10 +187,6 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
return myTargetResourceUrl;
|
||||
}
|
||||
|
||||
public Long getTargetResourcePid() {
|
||||
return myTargetResourcePid;
|
||||
}
|
||||
|
||||
public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
|
||||
Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
|
||||
Validate.isTrue(theTargetResourceUrl.hasResourceType());
|
||||
|
@ -199,6 +204,10 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
myTargetResourceUrl = theTargetResourceUrl.getValue();
|
||||
}
|
||||
|
||||
public Long getTargetResourcePid() {
|
||||
return myTargetResourcePid;
|
||||
}
|
||||
|
||||
public void setTargetResourceUrlCanonical(String theTargetResourceUrl) {
|
||||
Validate.notBlank(theTargetResourceUrl);
|
||||
|
||||
|
@ -279,11 +288,15 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated) {
|
||||
/**
|
||||
* @param theTargetResourceVersion This should only be populated if the reference actually had a version
|
||||
*/
|
||||
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated, @Nullable Long theTargetResourceVersion) {
|
||||
ResourceLink retVal = new ResourceLink();
|
||||
retVal.setSourcePath(theSourcePath);
|
||||
retVal.setSourceResource(theSourceResource);
|
||||
retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
|
||||
retVal.setTargetResourceVersion(theTargetResourceVersion);
|
||||
retVal.setUpdated(theUpdated);
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -162,8 +162,9 @@ public class SearchParameterMap implements Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
public void addInclude(Include theInclude) {
|
||||
public SearchParameterMap addInclude(Include theInclude) {
|
||||
getIncludes().add(theInclude);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addLastUpdateParam(StringBuilder b, DateParam date) {
|
||||
|
|
|
@ -268,8 +268,8 @@ public class SearchParamExtractorService {
|
|||
}
|
||||
|
||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||
String id = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
String targetId = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(targetId)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
|
@ -279,9 +279,11 @@ public class SearchParamExtractorService {
|
|||
}
|
||||
}
|
||||
|
||||
ResourcePersistentId resolvedTargetId = theTransactionDetails.getResolvedResourceIds().get(thePathAndRef.getRef().getReferenceElement());
|
||||
IIdType referenceElement = thePathAndRef.getRef().getReferenceElement();
|
||||
ResourcePersistentId resolvedTargetId = theTransactionDetails.getResolvedResourceId(referenceElement);
|
||||
ResourceLink resourceLink;
|
||||
|
||||
Long targetVersionId = nextId.getVersionIdPartAsLong();
|
||||
if (resolvedTargetId != null) {
|
||||
|
||||
/*
|
||||
|
@ -289,7 +291,7 @@ public class SearchParamExtractorService {
|
|||
* need to resolve it again
|
||||
*/
|
||||
myResourceLinkResolver.validateTypeOrThrowException(type);
|
||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, resolvedTargetId.getIdAsLong(), nextId.getIdPart(), transactionDate);
|
||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, resolvedTargetId.getIdAsLong(), targetId, transactionDate, targetVersionId);
|
||||
|
||||
} else if (theFailOnInvalidReference) {
|
||||
|
||||
|
@ -305,7 +307,7 @@ public class SearchParamExtractorService {
|
|||
} else {
|
||||
// Cache the outcome in the current transaction in case there are more references
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(resourceLink.getTargetResourcePid());
|
||||
theTransactionDetails.addResolvedResourceId(thePathAndRef.getRef().getReferenceElement(), persistentId);
|
||||
theTransactionDetails.addResolvedResourceId(referenceElement, persistentId);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -317,7 +319,7 @@ public class SearchParamExtractorService {
|
|||
ResourceTable target;
|
||||
target = new ResourceTable();
|
||||
target.setResourceType(typeString);
|
||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, nextId.getIdPart(), transactionDate);
|
||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, targetId, transactionDate, targetVersionId);
|
||||
|
||||
}
|
||||
|
||||
|
@ -346,7 +348,8 @@ public class SearchParamExtractorService {
|
|||
String targetResourceType = targetResource.getResourceType();
|
||||
Long targetResourcePid = targetResource.getResourceId();
|
||||
String targetResourceIdPart = theNextId.getIdPart();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime);
|
||||
Long targetVersion = theNextId.getVersionIdPartAsLong();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
|
||||
}
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue