Allow JPA server to restore resources and link to them in a single
transaction
This commit is contained in:
parent
9906243d2d
commit
b1283791ca
|
@ -1253,6 +1253,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
IBaseResource oldResource = toResource(entity, false);
|
||||
|
||||
/*
|
||||
* Mark the entity as not deleted - This is also done in the actual updateInternal()
|
||||
* method later on so it usually doesn't matter whether we do it here, but in the
|
||||
* case of a transaction with multiple PUTs we don't get there until later so
|
||||
* having this here means that a transaction can have a reference in one
|
||||
* resource to another resource in the same transaction that is being
|
||||
* un-deleted by the transaction. Wacky use case, sure. But it's real.
|
||||
*
|
||||
* See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources
|
||||
* for a test that needs this.
|
||||
*/
|
||||
entity.setDeleted(null);
|
||||
|
||||
/*
|
||||
* If we aren't indexing, that means we're doing this inside a transaction.
|
||||
* The transaction will do the actual storage to the database a bit later on,
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
package ca.uhn.fhir.jpa.provider;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||
import ca.uhn.fhir.jpa.rp.dstu2.*;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
|
||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -17,46 +30,32 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||
import ca.uhn.fhir.jpa.rp.dstu2.*;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
|
||||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu2Test.class);
|
||||
private static RestfulServer myRestServer;
|
||||
private static IGenericClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private static CloseableHttpClient ourHttpClient;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu2Test.class);
|
||||
private static Server ourServer;
|
||||
private static String ourServerBase;
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void beforeStartServer() throws Exception {
|
||||
if (myRestServer == null) {
|
||||
|
@ -72,9 +71,23 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
|
||||
organizationRp.setDao(myOrganizationDao);
|
||||
|
||||
LocationResourceProvider locationRp = new LocationResourceProvider();
|
||||
locationRp.setDao(myLocationDao);
|
||||
|
||||
BinaryResourceProvider binaryRp = new BinaryResourceProvider();
|
||||
binaryRp.setDao(myBinaryDao);
|
||||
|
||||
DiagnosticReportResourceProvider diagnosticReportRp = new DiagnosticReportResourceProvider();
|
||||
diagnosticReportRp.setDao(myDiagnosticReportDao);
|
||||
DiagnosticOrderResourceProvider diagnosticOrderRp = new DiagnosticOrderResourceProvider();
|
||||
diagnosticOrderRp.setDao(myDiagnosticOrderDao);
|
||||
PractitionerResourceProvider practitionerRp = new PractitionerResourceProvider();
|
||||
practitionerRp.setDao(myPractitionerDao);
|
||||
|
||||
|
||||
RestfulServer restServer = new RestfulServer(ourCtx);
|
||||
restServer.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(10));
|
||||
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
||||
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp, binaryRp, locationRp, diagnosticReportRp, diagnosticOrderRp, practitionerRp);
|
||||
|
||||
restServer.setPlainProviders(mySystemProvider);
|
||||
|
||||
|
@ -179,7 +192,7 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Transactional(propagation=Propagation.NEVER)
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
@Test
|
||||
public void testSuggestKeywords() throws Exception {
|
||||
|
||||
|
@ -269,6 +282,44 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
assertEquals("get-resource-counts", op.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionReSavesPreviouslyDeletedResources() throws IOException {
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ourLog.info("** Beginning pass {}", i);
|
||||
|
||||
Bundle input = myFhirCtx.newJsonParser().parseResource(Bundle.class, IOUtils.toString(getClass().getResourceAsStream("/dstu2/createdeletebundle.json"), Charsets.UTF_8));
|
||||
ourClient.transaction().withBundle(input).execute();
|
||||
|
||||
myPatientDao.read(new IdType("Patient/Patient1063259"));
|
||||
|
||||
deleteAllOfType("Binary");
|
||||
deleteAllOfType("Location");
|
||||
deleteAllOfType("DiagnosticReport");
|
||||
deleteAllOfType("Observation");
|
||||
deleteAllOfType("DiagnosticOrder");
|
||||
deleteAllOfType("Practitioner");
|
||||
deleteAllOfType("Patient");
|
||||
deleteAllOfType("Organization");
|
||||
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/Patient1063259"));
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void deleteAllOfType(String theType) {
|
||||
BundleUtil.toListOfResources(myFhirCtx, ourClient.search().forResource(theType).execute())
|
||||
.forEach(t -> {
|
||||
ourClient.delete().resourceById(t.getIdElement()).execute();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionFromBundle() throws Exception {
|
||||
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/transaction_link_patient_eve.xml");
|
||||
|
@ -372,7 +423,7 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
|
||||
@Test
|
||||
public void testTransactionSearch() throws Exception {
|
||||
for (int i = 0; i < 20; i ++) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily("PATIENT_" + i);
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
@ -385,7 +436,7 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
|
||||
|
||||
assertEquals(1, resp.getEntry().size());
|
||||
Bundle respSub = (Bundle)resp.getEntry().get(0).getResource();
|
||||
Bundle respSub = (Bundle) resp.getEntry().get(0).getResource();
|
||||
assertEquals("self", respSub.getLink().get(0).getRelation());
|
||||
assertEquals(ourServerBase + "/Patient", respSub.getLink().get(0).getUrl());
|
||||
assertEquals("next", respSub.getLink().get(1).getRelation());
|
||||
|
@ -396,7 +447,7 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
|
||||
@Test
|
||||
public void testTransactionCount() throws Exception {
|
||||
for (int i = 0; i < 20; i ++) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily("PATIENT_" + i);
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
@ -409,7 +460,7 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
|
||||
|
||||
assertEquals(1, resp.getEntry().size());
|
||||
Bundle respSub = (Bundle)resp.getEntry().get(0).getResource();
|
||||
Bundle respSub = (Bundle) resp.getEntry().get(0).getResource();
|
||||
assertEquals(20, respSub.getTotal().intValue());
|
||||
assertEquals(0, respSub.getEntry().size());
|
||||
}
|
||||
|
@ -423,9 +474,16 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourLog.info(output);
|
||||
assertEquals(200, http.getStatusLine().getStatusCode());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(http);;
|
||||
IOUtils.closeQuietly(http);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider;
|
||||
import ca.uhn.fhir.jpa.rp.r4.OrganizationResourceProvider;
|
||||
import ca.uhn.fhir.jpa.rp.r4.PatientResourceProvider;
|
||||
import ca.uhn.fhir.jpa.rp.r4.*;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
|
@ -17,8 +15,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
|
@ -42,6 +42,7 @@ import org.junit.*;
|
|||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -88,8 +89,21 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
|
|||
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
|
||||
organizationRp.setDao(myOrganizationDao);
|
||||
|
||||
LocationResourceProvider locationRp = new LocationResourceProvider();
|
||||
locationRp.setDao(myLocationDao);
|
||||
|
||||
BinaryResourceProvider binaryRp = new BinaryResourceProvider();
|
||||
binaryRp.setDao(myBinaryDao);
|
||||
|
||||
DiagnosticReportResourceProvider diagnosticReportRp = new DiagnosticReportResourceProvider();
|
||||
diagnosticReportRp.setDao(myDiagnosticReportDao);
|
||||
ServiceRequestResourceProvider diagnosticOrderRp = new ServiceRequestResourceProvider();
|
||||
diagnosticOrderRp.setDao(myServiceRequestDao);
|
||||
PractitionerResourceProvider practitionerRp = new PractitionerResourceProvider();
|
||||
practitionerRp.setDao(myPractitionerDao);
|
||||
|
||||
RestfulServer restServer = new RestfulServer(ourCtx);
|
||||
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
|
||||
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp, locationRp, binaryRp, diagnosticReportRp, diagnosticOrderRp, practitionerRp);
|
||||
|
||||
restServer.setPlainProviders(mySystemProvider);
|
||||
|
||||
|
@ -385,6 +399,45 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
|
|||
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionReSavesPreviouslyDeletedResources() throws IOException {
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ourLog.info("** Beginning pass {}", i);
|
||||
|
||||
Bundle input = myFhirCtx.newJsonParser().parseResource(Bundle.class, IOUtils.toString(getClass().getResourceAsStream("/r4/createdeletebundle.json"), Charsets.UTF_8));
|
||||
ourClient.transaction().withBundle(input).execute();
|
||||
|
||||
myPatientDao.read(new IdType("Patient/Patient1063259"));
|
||||
|
||||
deleteAllOfType("Binary");
|
||||
deleteAllOfType("Location");
|
||||
deleteAllOfType("DiagnosticReport");
|
||||
deleteAllOfType("Observation");
|
||||
deleteAllOfType("ServiceRequest");
|
||||
deleteAllOfType("Practitioner");
|
||||
deleteAllOfType("Patient");
|
||||
deleteAllOfType("Organization");
|
||||
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/Patient1063259"));
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void deleteAllOfType(String theType) {
|
||||
BundleUtil.toListOfResources(myFhirCtx, ourClient.search().forResource(theType).execute())
|
||||
.forEach(t -> {
|
||||
ourClient.delete().resourceById(t.getIdElement()).execute();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionDeleteWithDuplicateDeletes() throws Exception {
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "Patient/Patient1063259",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "Patient1063259",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "http://www.foo.com/fhir/identifier-type/EnterpriseId",
|
||||
"value": "1063259"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Patient/Patient1063259"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "DiagnosticReport/ReportSPATH",
|
||||
"resource": {
|
||||
"resourceType": "DiagnosticReport",
|
||||
"id": "ReportSPATH",
|
||||
"subject": {
|
||||
"reference": "Patient/Patient1063259"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "DiagnosticReport/ReportSPATH"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -146,6 +146,11 @@
|
|||
The JPA server version migrator tool now runs in a multithreaded way, allowing it to
|
||||
upgrade th database faster when migration tasks require data updates.
|
||||
</action>
|
||||
<action type="fix">
|
||||
A bug in the JPA server was fixed: When a resource was previously deleted,
|
||||
a transaction could not be posted that both restored the deleted resource but
|
||||
also contained references to the now-restored resource.
|
||||
</action>
|
||||
</release>
|
||||
|
||||
<release version="3.5.0" date="2018-09-17">
|
||||
|
|
Loading…
Reference in New Issue