diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index e7dd916e9fb..d48cb656c67 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1508,6 +1508,7 @@ public abstract class BaseHapiFhirDao implements IDao { if (theResource != null) { populateResourceIdFromEntity(theEntity, theResource); } + theEntity.setUnchangedInCurrentOperation(true); return theEntity; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 5f6acb32b2d..5d1a7cf4120 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -1127,7 +1127,7 @@ public abstract class BaseHapiFhirResourceDao extends B * we'll manually increase the version. This is important because we want the updated version number * to be reflected in the resource shared with interceptors */ - if (!thePerformIndexing) { + if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation()) { if (resourceId.hasVersionIdPart() == false) { resourceId = resourceId.withVersion(Long.toString(savedEntity.getVersion())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index bf6406941e3..1bb19109aca 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -242,6 +242,9 @@ public class ResourceTable extends BaseHasResource implements Serializable { @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private Set myTags; + @Transient + private transient boolean myUnchangedInCurrentOperation; + @Column(name = "RES_VER") private long myVersion; @@ -398,6 +401,14 @@ public class ResourceTable extends BaseHasResource implements Serializable { return myParamsUriPopulated; } + /** + * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation + * and was not re-saved in the database + */ + public boolean isUnchangedInCurrentOperation() { + return myUnchangedInCurrentOperation; + } + public void setContentTextParsedIntoWords(String theContentText) { myContentText = theContentText; } @@ -532,6 +543,14 @@ public class ResourceTable extends BaseHasResource implements Serializable { myResourceType = theResourceType; } + /** + * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation + * and was not re-saved in the database + */ + public void setUnchangedInCurrentOperation(boolean theUnchangedInCurrentOperation) { + myUnchangedInCurrentOperation = theUnchangedInCurrentOperation; + } + public void setVersion(long theVersion) { myVersion = theVersion; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index 9675befad55..35504a1c0f1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -23,23 +23,59 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.hamcrest.Matchers; import org.hamcrest.core.StringContains; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Age; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.CompartmentDefinition; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.DateType; +import org.hl7.fhir.dstu3.model.Device; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.NamingSystem; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.OperationDefinition; +import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Period; +import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Quantity.QuantityComparator; +import org.hl7.fhir.dstu3.model.Questionnaire; +import org.hl7.fhir.dstu3.model.Range; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.SimpleQuantity; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.Timing; +import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -49,14 +85,22 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.*; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; import com.google.common.base.Charsets; import com.google.common.collect.Lists; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; @@ -67,10 +111,21 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.TestUtil; @@ -258,7 +313,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { final IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); TransactionTemplate tx = new TransactionTemplate(myTxManager); - tx.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); tx.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theStatus) { @@ -593,15 +648,15 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { * If any of this ever fails, it means that one of the OperationOutcome issue severity codes has changed code value across versions. We store the string as a constant, so something will need to * be fixed. */ - assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR); - assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.ERROR.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR); - assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_INFO); - assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.INFORMATION.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_INFO); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_INFO); - assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN); - assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.WARNING.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN); + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); + assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.ERROR.getCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); + assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.INFORMATION.getCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); + assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.WARNING.getCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); } @Test @@ -2010,7 +2065,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { * See #534 */ @Test - public void testLogicalReferencesAreSearchable() throws IOException { + public void testLogicalReferencesAreSearchable() { myDaoConfig.setTreatReferencesAsLogical(null); myDaoConfig.addTreatReferencesAsLogical("http://foo.com/identifier*"); @@ -2288,7 +2343,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } @Test - public void testReadForcedIdVersionHistory() throws InterruptedException { + public void testReadForcedIdVersionHistory() { Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testReadVorcedIdVersionHistory01"); p1.setId("testReadVorcedIdVersionHistory"); @@ -2809,21 +2864,21 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { pm = new SearchParameterMap(); pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); - pm.setSort(new SortSpec(BaseResource.SP_RES_ID)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID)); actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); assertEquals(5, actual.size()); assertThat(actual, contains(idMethodName, id1, id2, id3, id4)); pm = new SearchParameterMap(); pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); - pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.ASC)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.ASC)); actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); assertEquals(5, actual.size()); assertThat(actual, contains(idMethodName, id1, id2, id3, id4)); pm = new SearchParameterMap(); pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName)); - pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.DESC)); + pm.setSort(new SortSpec(IAnyResource.SP_RES_ID).setOrder(SortOrderEnum.DESC)); actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm)); assertEquals(5, actual.size()); assertThat(actual, contains(id4, id3, id2, id1, idMethodName)); @@ -3218,13 +3273,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { SearchParameterMap map; map = new SearchParameterMap(); - map.add(BaseResource.SP_RES_ID, new StringParam(id1.getIdPart())); + map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart())); map.setLastUpdated(new DateRangeParam("2001", "2003")); map.setSort(new SortSpec(Constants.PARAM_LASTUPDATED)); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), empty()); map = new SearchParameterMap(); - map.add(BaseResource.SP_RES_ID, new StringParam(id1.getIdPart())); + map.add(IAnyResource.SP_RES_ID, new StringParam(id1.getIdPart())); map.setLastUpdated(new DateRangeParam("2001", "2003")); map.setSort(new SortSpec(Patient.SP_NAME)); assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), empty()); @@ -3361,7 +3416,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { assertEquals("Kittens", published.get(1).getDisplay()); assertEquals("http://foo", published.get(1).getSystem()); - secLabels = (ArrayList) retrieved.getMeta().getSecurity(); + secLabels = retrieved.getMeta().getSecurity(); sortCodings(secLabels); assertEquals(2, secLabels.size()); assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java index 94cd1b849f8..cda43e6288c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java @@ -46,19 +46,130 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3UpdateTest.class); - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); + @Test + public void testCreateAndUpdateWithoutRequest() throws Exception { + String methodName = "testUpdateByUrl"; + + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + IIdType id = myPatientDao.create(p).getId().toUnqualified(); + + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + p.setActive(true); + IIdType id2 = myPatientDao.create(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); + assertEquals(id.getValue(), id2.getValue()); + + p = new Patient(); + p.setId(id); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + p.setActive(false); + myPatientDao.update(p).getId(); + + p.setActive(true); + id2 = myPatientDao.update(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); + assertEquals(id.getIdPart(), id2.getIdPart()); + assertEquals("3", id2.getVersionIdPart()); + + Patient newPatient = myPatientDao.read(id); + assertEquals("1", newPatient.getIdElement().getVersionIdPart()); + + newPatient = myPatientDao.read(id.toVersionless()); + assertEquals("3", newPatient.getIdElement().getVersionIdPart()); + + myPatientDao.delete(id.toVersionless()); + + try { + myPatientDao.read(id.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // nothing + } + } + @Test + public void testDuplicateProfilesIgnored() { + String name = "testDuplicateProfilesIgnored"; + IIdType id; + { + Patient patient = new Patient(); + patient.addName().setFamily(name); + + List tl = new ArrayList(); + tl.add(new IdType("http://foo/bar")); + tl.add(new IdType("http://foo/bar")); + tl.add(new IdType("http://foo/bar")); + patient.getMeta().getProfile().addAll(tl); + + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getProfile(); + assertEquals(1, tl.size()); + assertEquals("http://foo/bar", tl.get(0).getValue()); + } + + } + + @Test + public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForDiscreteUpdates() { + + // First time + Patient p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + String id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + // Second time should not result in an update + p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + // And third time should not result in an update + p = new Patient(); + p.setActive(true); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/1")); + + myPatientDao.read(new IdType("Patient/A")); + myPatientDao.read(new IdType("Patient/A/_history/1")); + try { + myPatientDao.read(new IdType("Patient/A/_history/2")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + try { + myPatientDao.read(new IdType("Patient/A/_history/3")); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + + // Create one more + p = new Patient(); + p.setActive(false); + p.setId("Patient/A"); + id = myPatientDao.update(p).getId().getValue(); + assertThat(id, endsWith("Patient/A/_history/2")); + + } @Test public void testUpdateAndGetHistoryResource() throws InterruptedException { @@ -113,14 +224,14 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { IBundleProvider historyBundle = myPatientDao.history(outcome.getId(), null, null, mySrd); assertEquals(2, historyBundle.size().intValue()); - + List history = historyBundle.getResources(0, 2); - + ourLog.info("updated : {}", updated.getValueAsString()); ourLog.info(" * Exp : {}", ((Resource) history.get(1)).getMeta().getLastUpdatedElement().getValueAsString()); ourLog.info("updated2: {}", updated2.getValueAsString()); ourLog.info(" * Exp : {}", ((Resource) history.get(0)).getMeta().getLastUpdatedElement().getValueAsString()); - + assertEquals("1", history.get(1).getIdElement().getVersionIdPart()); assertEquals("2", history.get(0).getIdElement().getVersionIdPart()); assertEquals(updated.getValueAsString(), ((Resource) history.get(1)).getMeta().getLastUpdatedElement().getValueAsString()); @@ -154,49 +265,6 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } - @Test - public void testCreateAndUpdateWithoutRequest() throws Exception { - String methodName = "testUpdateByUrl"; - - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); - IIdType id = myPatientDao.create(p).getId().toUnqualified(); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); - p.setActive(true); - IIdType id2 = myPatientDao.create(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); - assertEquals(id.getValue(), id2.getValue()); - - p = new Patient(); - p.setId(id); - p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); - p.setActive(false); - myPatientDao.update(p).getId(); - - p.setActive(true); - id2 = myPatientDao.update(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified(); - assertEquals(id.getIdPart(), id2.getIdPart()); - assertEquals("3", id2.getVersionIdPart()); - - Patient newPatient = myPatientDao.read(id); - assertEquals("1", newPatient.getIdElement().getVersionIdPart()); - - newPatient = myPatientDao.read(id.toVersionless()); - assertEquals("3", newPatient.getIdElement().getVersionIdPart()); - - myPatientDao.delete(id.toVersionless()); - - try { - myPatientDao.read(id.toVersionless()); - fail(); - } catch (ResourceGoneException e) { - // nothing - } - - } - - @Test public void testUpdateConditionalByLastUpdated() throws Exception { String methodName = "testUpdateByUrl"; @@ -208,7 +276,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { InstantDt start = InstantDt.withCurrentTime(); ourLog.info("First time: {}", start.getValueAsString()); Thread.sleep(100); - + p = new Patient(); p.addIdentifier().setSystem("urn:system").setValue(methodName); IIdType id = myPatientDao.create(p, mySrd).getId(); @@ -237,35 +305,35 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { public void testUpdateConditionalByLastUpdatedWithWrongTimezone() throws Exception { TimeZone def = TimeZone.getDefault(); try { - TimeZone.setDefault(TimeZone.getTimeZone("GMT-0:00")); - String methodName = "testUpdateByUrl"; + TimeZone.setDefault(TimeZone.getTimeZone("GMT-0:00")); + String methodName = "testUpdateByUrl"; - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); - myPatientDao.create(p, mySrd).getId(); + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName + "2"); + myPatientDao.create(p, mySrd).getId(); - InstantDt start = InstantDt.withCurrentTime(); - Thread.sleep(100); - - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - IIdType id = myPatientDao.create(p, mySrd).getId(); - ourLog.info("Created patient, got it: {}", id); + InstantDt start = InstantDt.withCurrentTime(); + Thread.sleep(100); - Thread.sleep(100); + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + IIdType id = myPatientDao.create(p, mySrd).getId(); + ourLog.info("Created patient, got it: {}", id); - p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue(methodName); - p.addName().setFamily("Hello"); - p.setId("Patient/" + methodName); + Thread.sleep(100); - myPatientDao.update(p, "Patient?_lastUpdated=gt" + start.getValueAsString(), mySrd); + p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue(methodName); + p.addName().setFamily("Hello"); + p.setId("Patient/" + methodName); - p = myPatientDao.read(id.toVersionless(), mySrd); - assertThat(p.getIdElement().toVersionless().toString(), not(containsString("test"))); - assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); - assertNotEquals(id, p.getIdElement()); - assertThat(p.getIdElement().toString(), endsWith("/_history/2")); + myPatientDao.update(p, "Patient?_lastUpdated=gt" + start.getValueAsString(), mySrd); + + p = myPatientDao.read(id.toVersionless(), mySrd); + assertThat(p.getIdElement().toVersionless().toString(), not(containsString("test"))); + assertEquals(id.toVersionless(), p.getIdElement().toVersionless()); + assertNotEquals(id, p.getIdElement()); + assertThat(p.getIdElement().toString(), endsWith("/_history/2")); } finally { TimeZone.setDefault(def); } @@ -296,57 +364,27 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { myPatientDao.update(p, mySrd); } - /** - * Per the spec, update should preserve tags and security labels but not profiles - */ @Test - public void testUpdateMaintainsTagsAndSecurityLabels() throws InterruptedException { - String methodName = "testUpdateMaintainsTagsAndSecurityLabels"; + @Ignore + public void testUpdateIgnoresIdenticalVersions() { + String methodName = "testUpdateIgnoresIdenticalVersions"; - IIdType p1id; - { - Patient p1 = new Patient(); - p1.addName().setFamily(methodName); - - p1.getMeta().addTag("tag_scheme1", "tag_term1",null); - p1.getMeta().addSecurity("sec_scheme1", "sec_term1",null); - p1.getMeta().addProfile("http://foo1"); + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue(methodName); + p1.addName().setFamily("Tester").addGiven(methodName); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); - p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); - } - { - Patient p1 = new Patient(); - p1.setId(p1id); - p1.addName().setFamily(methodName); + IIdType p1id2 = myPatientDao.update(p1, mySrd).getId(); + assertEquals(p1id.getValue(), p1id2.getValue()); - p1.getMeta().addTag("tag_scheme2", "tag_term2", null); - p1.getMeta().addSecurity("sec_scheme2", "sec_term2", null); - p1.getMeta().addProfile("http://foo2"); + p1.addName().addGiven("NewGiven"); + IIdType p1id3 = myPatientDao.update(p1, mySrd).getId(); + assertNotEquals(p1id.getValue(), p1id3.getValue()); - myPatientDao.update(p1, mySrd); - } - { - Patient p1 = myPatientDao.read(p1id, mySrd); - List tagList = p1.getMeta().getTag(); - Set secListValues = new HashSet(); - for (Coding next : tagList) { - secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); - } - assertThat(secListValues, containsInAnyOrder("tag_scheme1|tag_term1", "tag_scheme2|tag_term2")); - List secList = p1.getMeta().getSecurity(); - secListValues = new HashSet(); - for (Coding next : secList) { - secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); - } - assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2")); - List profileList = p1.getMeta().getProfile(); - assertEquals(1, profileList.size()); - assertEquals("http://foo2", profileList.get(0).getValueAsString()); // no foo1 - } } @Test - public void testUpdateMaintainsSearchParams() throws InterruptedException { + public void testUpdateMaintainsSearchParams() { Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2AAA"); p1.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2AAA"); @@ -381,77 +419,53 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } + /** + * Per the spec, update should preserve tags and security labels but not profiles + */ @Test - public void testUpdateRejectsInvalidTypes() throws InterruptedException { - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes"); - p1.addName().setFamily("Tester").addGiven("testUpdateRejectsInvalidTypes"); - IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + public void testUpdateMaintainsTagsAndSecurityLabels() { + String methodName = "testUpdateMaintainsTagsAndSecurityLabels"; - Organization p2 = new Organization(); - p2.getNameElement().setValue("testUpdateRejectsInvalidTypes"); - try { - p2.setId(new IdType("Organization/" + p1id.getIdPart())); - myOrganizationDao.update(p2, mySrd); - fail(); - } catch (UnprocessableEntityException e) { - // good - } - - try { - p2.setId(new IdType("Patient/" + p1id.getIdPart())); - myOrganizationDao.update(p2, mySrd); - fail(); - } catch (UnprocessableEntityException e) { - ourLog.error("Good", e); - } - - } - - @Test - @Ignore - public void testUpdateIgnoresIdenticalVersions() throws InterruptedException { - String methodName = "testUpdateIgnoresIdenticalVersions"; - - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue(methodName); - p1.addName().setFamily("Tester").addGiven(methodName); - IIdType p1id = myPatientDao.create(p1, mySrd).getId(); - - IIdType p1id2 = myPatientDao.update(p1, mySrd).getId(); - assertEquals(p1id.getValue(), p1id2.getValue()); - - p1.addName().addGiven("NewGiven"); - IIdType p1id3 = myPatientDao.update(p1, mySrd).getId(); - assertNotEquals(p1id.getValue(), p1id3.getValue()); - - } - - @Test - public void testDuplicateProfilesIgnored() { - String name = "testDuplicateProfilesIgnored"; - IIdType id; + IIdType p1id; { - Patient patient = new Patient(); - patient.addName().setFamily(name); + Patient p1 = new Patient(); + p1.addName().setFamily(methodName); - List tl = new ArrayList(); - tl.add(new IdType("http://foo/bar")); - tl.add(new IdType("http://foo/bar")); - tl.add(new IdType("http://foo/bar")); - patient.getMeta().getProfile().addAll(tl); + p1.getMeta().addTag("tag_scheme1", "tag_term1", null); + p1.getMeta().addSecurity("sec_scheme1", "sec_term1", null); + p1.getMeta().addProfile("http://foo1"); - id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); } - - // Do a read { - Patient patient = myPatientDao.read(id, mySrd); - List tl = patient.getMeta().getProfile(); - assertEquals(1, tl.size()); - assertEquals("http://foo/bar", tl.get(0).getValue()); - } + Patient p1 = new Patient(); + p1.setId(p1id); + p1.addName().setFamily(methodName); + p1.getMeta().addTag("tag_scheme2", "tag_term2", null); + p1.getMeta().addSecurity("sec_scheme2", "sec_term2", null); + p1.getMeta().addProfile("http://foo2"); + + myPatientDao.update(p1, mySrd); + } + { + Patient p1 = myPatientDao.read(p1id, mySrd); + List tagList = p1.getMeta().getTag(); + Set secListValues = new HashSet(); + for (Coding next : tagList) { + secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); + } + assertThat(secListValues, containsInAnyOrder("tag_scheme1|tag_term1", "tag_scheme2|tag_term2")); + List secList = p1.getMeta().getSecurity(); + secListValues = new HashSet(); + for (Coding next : secList) { + secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); + } + assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2")); + List profileList = p1.getMeta().getProfile(); + assertEquals(1, profileList.size()); + assertEquals("http://foo2", profileList.get(0).getValueAsString()); // no foo1 + } } @Test @@ -501,6 +515,33 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } + @Test + public void testUpdateRejectsInvalidTypes() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes"); + p1.addName().setFamily("Tester").addGiven("testUpdateRejectsInvalidTypes"); + IIdType p1id = myPatientDao.create(p1, mySrd).getId(); + + Organization p2 = new Organization(); + p2.getNameElement().setValue("testUpdateRejectsInvalidTypes"); + try { + p2.setId(new IdType("Organization/" + p1id.getIdPart())); + myOrganizationDao.update(p2, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + // good + } + + try { + p2.setId(new IdType("Patient/" + p1id.getIdPart())); + myOrganizationDao.update(p2, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + ourLog.error("Good", e); + } + + } + @Test public void testUpdateUnknownNumericIdFails() { Patient p = new Patient(); @@ -529,63 +570,10 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } } - @Test - public void testUpdateWithNumericIdFails() { - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); - p.addName().setFamily("Hello"); - p.setId("Patient/123"); - try { - myPatientDao.update(p, mySrd); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("clients may only assign IDs which contain at least one non-numeric")); - } - } - - @Test - public void testUpdateWithNumericThenTextIdSucceeds() { - Patient p = new Patient(); - p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); - p.addName().setFamily("Hello"); - p.setId("Patient/123abc"); - IIdType id = myPatientDao.update(p, mySrd).getId(); - assertEquals("123abc", id.getIdPart()); - assertEquals("1", id.getVersionIdPart()); - - p = myPatientDao.read(id.toUnqualifiedVersionless(), mySrd); - assertEquals("Patient/123abc", p.getIdElement().toUnqualifiedVersionless().getValue()); - assertEquals("Hello", p.getName().get(0).getFamily()); - - } - - - @Test - public void testUpdateWithNoChangeDetectionUpdateUnchanged() { - String name = "testUpdateUnchanged"; - IIdType id1, id2; - { - Patient patient = new Patient(); - patient.addName().setFamily(name); - id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); - } - - // Update - { - Patient patient = new Patient(); - patient.setId(id1); - patient.addName().setFamily(name); - id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); - } - - assertEquals(id1.getValue(), id2.getValue()); - } - - @Test public void testUpdateWithNoChangeDetectionDisabledUpdateUnchanged() { myDaoConfig.setSuppressUpdatesWithNoChange(false); - + String name = "testUpdateUnchanged"; IIdType id1, id2; { @@ -626,7 +614,39 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { assertNotEquals(id1.getValue(), id2.getValue()); } - + + @Test + public void testUpdateWithNoChangeDetectionUpdateTagMetaRemoved() { + String name = "testUpdateUnchanged"; + IIdType id1, id2; + { + Patient patient = new Patient(); + patient.getMeta().addTag().setCode("CODE"); + patient.addName().setFamily(name); + id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); + } + + Meta meta = new Meta(); + meta.addTag().setCode("CODE"); + myPatientDao.metaDeleteOperation(id1, meta, null); + + meta = myPatientDao.metaGetOperation(Meta.class, id1, null); + assertEquals(0, meta.getTag().size()); + + // Update + { + Patient patient = new Patient(); + patient.setId(id1); + patient.addName().setFamily(name); + id2 = myPatientDao.update(patient, mySrd).getId().toUnqualified(); + } + + assertEquals(id1.getValue(), id2.getValue()); + + meta = myPatientDao.metaGetOperation(Meta.class, id2, null); + assertEquals(0, meta.getTag().size()); + } + @Test public void testUpdateWithNoChangeDetectionUpdateTagNoChange() { String name = "testUpdateUnchanged"; @@ -641,7 +661,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { Meta meta = new Meta(); meta.addTag().setCode("CODE"); myPatientDao.metaAddOperation(id1, meta, null); - + // Update with tag { Patient patient = new Patient(); @@ -652,7 +672,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } assertEquals(id1.getValue(), id2.getValue()); - + meta = myPatientDao.metaGetOperation(Meta.class, id2, null); assertEquals(1, meta.getTag().size()); assertEquals("CODE", meta.getTag().get(0).getCode()); @@ -678,31 +698,22 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } assertEquals(id1.getValue(), id2.getValue()); - + Meta meta = myPatientDao.metaGetOperation(Meta.class, id2, null); assertEquals(1, meta.getTag().size()); assertEquals("CODE", meta.getTag().get(0).getCode()); } @Test - public void testUpdateWithNoChangeDetectionUpdateTagMetaRemoved() { + public void testUpdateWithNoChangeDetectionUpdateUnchanged() { String name = "testUpdateUnchanged"; IIdType id1, id2; { Patient patient = new Patient(); - patient.getMeta().addTag().setCode("CODE"); patient.addName().setFamily(name); id1 = myPatientDao.create(patient, mySrd).getId().toUnqualified(); } - Meta meta = new Meta(); - meta.addTag().setCode("CODE"); - myPatientDao.metaDeleteOperation(id1, meta, null); - - - meta = myPatientDao.metaGetOperation(Meta.class, id1, null); - assertEquals(0, meta.getTag().size()); - // Update { Patient patient = new Patient(); @@ -712,9 +723,41 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } assertEquals(id1.getValue(), id2.getValue()); - - meta = myPatientDao.metaGetOperation(Meta.class, id2, null); - assertEquals(0, meta.getTag().size()); + } + + @Test + public void testUpdateWithNumericIdFails() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/123"); + try { + myPatientDao.update(p, mySrd); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("clients may only assign IDs which contain at least one non-numeric")); + } + } + + @Test + public void testUpdateWithNumericThenTextIdSucceeds() { + Patient p = new Patient(); + p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); + p.addName().setFamily("Hello"); + p.setId("Patient/123abc"); + IIdType id = myPatientDao.update(p, mySrd).getId(); + assertEquals("123abc", id.getIdPart()); + assertEquals("1", id.getVersionIdPart()); + + p = myPatientDao.read(id.toUnqualifiedVersionless(), mySrd); + assertEquals("Patient/123abc", p.getIdElement().toUnqualifiedVersionless().getValue()); + assertEquals("Hello", p.getName().get(0).getFamily()); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 2d3162df211..b6c9bf688d4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -24,19 +24,33 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; - -import javax.mail.Quota.Resource; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Appointment; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.EpisodeOfCare; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Medication; +import org.hl7.fhir.dstu3.model.MedicationRequest; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; @@ -58,14 +72,17 @@ import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; -import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.TestUtil; @@ -73,37 +90,15 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu3Test.class); - @Before - public void beforeDisableResultReuse() { - myDaoConfig.setReuseCachedSearchResultsForMillis(null); - } - @After public void after() { myDaoConfig.setAllowInlineMatchUrlReferences(false); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); } - @Test - public void testTransactionWithMultiBundle() throws IOException { - String inputBundleString = loadClasspath("/batch-error.xml"); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, inputBundleString); - Bundle resp = mySystemDao.transaction(mySrd, bundle); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); - } - - @Test - public void testTransaction1() throws IOException { - String inputBundleString = loadClasspath("/david-bundle-error.json"); - Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputBundleString); - Bundle resp = mySystemDao.transaction(mySrd, bundle); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); } @SuppressWarnings("unchecked") @@ -122,6 +117,86 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { return null; } + @Test + public void testCircularCreateAndDelete() { + Encounter enc = new Encounter(); + enc.setId(IdType.newRandomUuid()); + + Condition cond = new Condition(); + cond.setId(IdType.newRandomUuid()); + + EpisodeOfCare ep = new EpisodeOfCare(); + ep.setId(IdType.newRandomUuid()); + + enc.getEpisodeOfCareFirstRep().setReference(ep.getId()); + cond.getContext().setReference(enc.getId()); + ep.getDiagnosisFirstRep().getCondition().setReference(cond.getId()); + + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + inputBundle + .addEntry() + .setResource(ep) + .setFullUrl(ep.getId()) + .getRequest().setMethod(HTTPVerb.POST); + inputBundle + .addEntry() + .setResource(cond) + .setFullUrl(cond.getId()) + .getRequest().setMethod(HTTPVerb.POST); + inputBundle + .addEntry() + .setResource(enc) + .setFullUrl(enc.getId()) + .getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + IdType epId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + IdType condId = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + IdType encId = new IdType(resp.getEntry().get(2).getResponse().getLocation()); + + // Make sure a single one can't be deleted + try { + myEncounterDao.delete(encId); + fail(); + } catch (ResourceVersionConflictException e) { + // good + } + + /* + * Now delete all 3 by transaction + */ + inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(epId.toUnqualifiedVersionless().getValue()); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(encId.toUnqualifiedVersionless().getValue()); + inputBundle + .addEntry() + .getRequest().setMethod(HTTPVerb.DELETE) + .setUrl(condId.toUnqualifiedVersionless().getValue()); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(inputBundle)); + resp = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + // They should now be deleted + try { + myEncounterDao.read(encId.toUnqualifiedVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good + } + + } + /** * See #410 */ @@ -138,26 +213,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(mo)); } - @Test - public void testTransactionOruBundle() throws IOException { - myDaoConfig.setAllowMultipleDelete(true); - - String input = IOUtils.toString(getClass().getResourceAsStream("/oruBundle.json"), StandardCharsets.UTF_8); - - Bundle inputBundle; - Bundle outputBundle; - inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); - outputBundle = mySystemDao.transaction(mySrd, inputBundle); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); - - inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); - outputBundle = mySystemDao.transaction(mySrd, inputBundle); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); - - IBundleProvider allPatients = myPatientDao.search(new SearchParameterMap()); - assertEquals(1, allPatients.size().intValue()); - } - @Test public void testDeleteWithHas() { Observation obs1 = new Observation(); @@ -195,6 +250,71 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { rpt = myDiagnosticReportDao.read(rptId); assertThat(rpt.getResult(), empty()); } + + @Test + public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForTransaction() { + Bundle bundle; + + // First time + Patient p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + // Second time should not result in an update + p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + // And third time should not result in an update + p = new Patient(); + p.setActive(true); + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + bundle + .addEntry() + .setResource(p) + .setFullUrl("Patient/A") + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Patient/A"); + resp = mySystemDao.transaction(mySrd, bundle); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1")); + + myPatientDao.read(new IdType("Patient/A")); + myPatientDao.read(new IdType("Patient/A/_history/1")); + try { + myPatientDao.read(new IdType("Patient/A/_history/2")); + fail(); + } catch (ResourceNotFoundException e) { + //good + } + try { + myPatientDao.read(new IdType("Patient/A/_history/3")); + fail(); + } catch (ResourceNotFoundException e) { + //good + } + } @Test public void testReindexing() { @@ -344,6 +464,17 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @Test + public void testTransaction1() throws IOException { + String inputBundleString = loadClasspath("/david-bundle-error.json"); + Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputBundleString); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + } + @Test public void testTransactionBatchWithFailingRead() { String methodName = "testTransactionBatchWithFailingRead"; @@ -411,42 +542,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } } - /** - * Per a message on the mailing list - */ - @Test - public void testTransactionWithPostDoesntUpdate() throws Exception { - - // First bundle (name is Joshua) - - String input = IOUtils.toString(getClass().getResource("/dstu3-post1.xml"), StandardCharsets.UTF_8); - Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - Bundle response = mySystemDao.transaction(mySrd, request); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - - assertEquals(1, response.getEntry().size()); - assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); - assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); - String id = response.getEntry().get(0).getResponse().getLocation(); - - // Now the second (name is Adam, shouldn't get used) - - input = IOUtils.toString(getClass().getResource("/dstu3-post2.xml"), StandardCharsets.UTF_8); - request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); - response = mySystemDao.transaction(mySrd, request); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - - assertEquals(1, response.getEntry().size()); - assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); - assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); - String id2 = response.getEntry().get(0).getResponse().getLocation(); - assertEquals(id, id2); - - Patient patient = myPatientDao.read(new IdType(id), mySrd); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient)); - assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString()); - } - @Test public void testTransactionCreateInlineMatchUrlWithOneMatch() { String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch"; @@ -612,7 +707,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); assertEquals("1", respEntry.getResponse().getEtag()); - o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); assertEquals("1", o.getIdElement().getVersionIdPart()); @@ -686,95 +781,10 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); assertEquals("1", respEntry.getResponse().getEtag()); - o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); + o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()), mySrd); assertEquals(new IdType(patientId).toUnqualifiedVersionless().getValue(), o.getSubject().getReference()); } - /** - * See #467 - */ - @Test - public void testTransactionWithSelfReferentialLink() { - /* - * Link to each other - */ - Bundle request = new Bundle(); - - Organization o1 = new Organization(); - o1.setId(IdType.newRandomUuid()); - o1.setName("ORG1"); - request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST); - - Organization o2 = new Organization(); - o2.setName("ORG2"); - o2.setId(IdType.newRandomUuid()); - request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST); - - o1.getPartOf().setReference(o2.getId()); - o2.getPartOf().setReference(o1.getId()); - - Bundle resp = mySystemDao.transaction(mySrd, request); - assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); - assertEquals(2, resp.getEntry().size()); - - IdType id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); - IdType id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); - - ourLog.info("ID1: {}", id1); - - SearchParameterMap map = new SearchParameterMap(); - map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); - IBundleProvider res = myOrganizationDao.search(map); - assertEquals(1, res.size().intValue()); - assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); - - map = new SearchParameterMap(); - map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); - res = myOrganizationDao.search(map); - assertEquals(1, res.size().intValue()); - assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); - - /* - * Link to self - */ - request = new Bundle(); - - o1 = new Organization(); - o1.setId(id1); - o1.setName("ORG1"); - request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.PUT).setUrl(id1.toUnqualifiedVersionless().getValue()); - - o2 = new Organization(); - o2.setName("ORG2"); - o2.setId(id2); - request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.PUT).setUrl(id2.toUnqualifiedVersionless().getValue()); - - o1.getPartOf().setReference(o1.getId()); - o2.getPartOf().setReference(o2.getId()); - - resp = mySystemDao.transaction(mySrd, request); - assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); - assertEquals(2, resp.getEntry().size()); - - id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); - id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); - - ourLog.info("ID1: {}", id1); - - map = new SearchParameterMap(); - map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); - res = myOrganizationDao.search(map); - assertEquals(1, res.size().intValue()); - assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); - - map = new SearchParameterMap(); - map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); - res = myOrganizationDao.search(map); - assertEquals(1, res.size().intValue()); - assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); - - } - @Test public void testTransactionCreateNoMatchUrl() { String methodName = "testTransactionCreateNoMatchUrl"; @@ -1300,6 +1310,67 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong()); } + @Test + public void testTransactionDoubleConditionalCreateOnlyCreatesOne() { + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + + Encounter enc1 = new Encounter(); + enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc1) + .getRequest() + .setMethod(HTTPVerb.POST) + .setIfNoneExist("Encounter?identifier=urn:foo|12345"); + Encounter enc2 = new Encounter(); + enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc2) + .getRequest() + .setMethod(HTTPVerb.POST) + .setIfNoneExist("Encounter?identifier=urn:foo|12345"); + + try { + mySystemDao.transaction(mySrd, inputBundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage()); + } + } + + @Test + public void testTransactionDoubleConditionalUpdateOnlyCreatesOne() { + Bundle inputBundle = new Bundle(); + inputBundle.setType(Bundle.BundleType.TRANSACTION); + + Encounter enc1 = new Encounter(); + enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc1) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Encounter?identifier=urn:foo|12345"); + Encounter enc2 = new Encounter(); + enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); + inputBundle + .addEntry() + .setResource(enc2) + .getRequest() + .setMethod(HTTPVerb.PUT) + .setUrl("Encounter?identifier=urn:foo|12345"); + + try { + mySystemDao.transaction(mySrd, inputBundle); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage()); + } + + } + @Test(expected = InvalidRequestException.class) public void testTransactionFailsWithDuplicateIds() { Bundle request = new Bundle(); @@ -1464,6 +1535,26 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(respGetBundle.getLink("self").getUrl(), endsWith("/Patient?identifier=testTransactionOrdering")); } + @Test + public void testTransactionOruBundle() throws IOException { + myDaoConfig.setAllowMultipleDelete(true); + + String input = IOUtils.toString(getClass().getResourceAsStream("/oruBundle.json"), StandardCharsets.UTF_8); + + Bundle inputBundle; + Bundle outputBundle; + inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + inputBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, input); + outputBundle = mySystemDao.transaction(mySrd, inputBundle); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outputBundle)); + + IBundleProvider allPatients = myPatientDao.search(new SearchParameterMap()); + assertEquals(1, allPatients.size().intValue()); + } + @Test public void testTransactionReadAndSearch() { String methodName = "testTransactionReadAndSearch"; @@ -1893,6 +1984,24 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @Test + public void testTransactionWIthInvalidPlaceholder() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Observation o1 = new Observation(); + o1.setId("cid:observation1"); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + try { + mySystemDao.transaction(mySrd, res); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid placeholder ID found: cid:observation1 - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", e.getMessage()); + } + } + @Test public void testTransactionWithInvalidType() { Bundle request = new Bundle(); @@ -1909,6 +2018,17 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { } + @Test + public void testTransactionWithMultiBundle() throws IOException { + String inputBundleString = loadClasspath("/batch-error.xml"); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, inputBundleString); + Bundle resp = mySystemDao.transaction(mySrd, bundle); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + } + @Test public void testTransactionWithNullReference() { Patient p = new Patient(); @@ -1967,113 +2087,40 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertEquals(id0.toUnqualifiedVersionless().getValue(), app2.getParticipant().get(1).getActor().getReference()); } + /** + * Per a message on the mailing list + */ @Test - public void testTransactionWithReferenceToCreateIfNoneExist() { - Bundle bundle = new Bundle(); - bundle.setType(BundleType.TRANSACTION); + public void testTransactionWithPostDoesntUpdate() throws Exception { - Medication med = new Medication(); - IdType medId = IdType.newRandomUuid(); - med.setId(medId); - med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); - bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); + // First bundle (name is Joshua) - MedicationRequest mo = new MedicationRequest(); - mo.setMedication(new Reference(medId)); - bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); + String input = IOUtils.toString(getClass().getResource("/dstu3-post1.xml"), StandardCharsets.UTF_8); + Bundle request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + Bundle response = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - ourLog.info("Request:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + assertEquals(1, response.getEntry().size()); + assertEquals("201 Created", response.getEntry().get(0).getResponse().getStatus()); + assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); + String id = response.getEntry().get(0).getResponse().getLocation(); - Bundle outcome = mySystemDao.transaction(mySrd, bundle); - ourLog.info("Response:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + // Now the second (name is Adam, shouldn't get used) - IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); - IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + input = IOUtils.toString(getClass().getResource("/dstu3-post2.xml"), StandardCharsets.UTF_8); + request = myFhirCtx.newXmlParser().parseResource(Bundle.class, input); + response = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - /* - * Again! - */ - - bundle = new Bundle(); - bundle.setType(BundleType.TRANSACTION); - - med = new Medication(); - medId = IdType.newRandomUuid(); - med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); - bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); - - mo = new MedicationRequest(); - mo.setMedication(new Reference(medId)); - bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); - - outcome = mySystemDao.transaction(mySrd, bundle); - - IdType medId2 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); - IdType medOrderId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); - - assertTrue(medId1.isIdPartValidLong()); - assertTrue(medId2.isIdPartValidLong()); - assertTrue(medOrderId1.isIdPartValidLong()); - assertTrue(medOrderId2.isIdPartValidLong()); - - assertEquals(medId1, medId2); - assertNotEquals(medOrderId1, medOrderId2); - } - - @Test - public void testTransactionWIthInvalidPlaceholder() throws Exception { - Bundle res = new Bundle(); - res.setType(BundleType.TRANSACTION); - - Observation o1 = new Observation(); - o1.setId("cid:observation1"); - o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); - res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); - - try { - mySystemDao.transaction(mySrd, res); - fail(); - } catch (InvalidRequestException e) { - assertEquals("Invalid placeholder ID found: cid:observation1 - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", e.getMessage()); - } - } - - @Test - public void testTransactionWithRelativeOidIds() throws Exception { - Bundle res = new Bundle(); - res.setType(BundleType.TRANSACTION); - - Patient p1 = new Patient(); - p1.setId("urn:oid:0.1.2.3"); - p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); - res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); - - Observation o1 = new Observation(); - o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); - o1.setSubject(new Reference("urn:oid:0.1.2.3")); - res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); - - Observation o2 = new Observation(); - o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); - o2.setSubject(new Reference("urn:oid:0.1.2.3")); - res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); - - Bundle resp = mySystemDao.transaction(mySrd, res); - - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); - assertEquals(3, resp.getEntry().size()); - - assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); - - o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); - o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); - assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); - assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + assertEquals(1, response.getEntry().size()); + assertEquals("200 OK", response.getEntry().get(0).getResponse().getStatus()); + assertEquals("1", response.getEntry().get(0).getResponse().getEtag()); + String id2 = response.getEntry().get(0).getResponse().getLocation(); + assertEquals(id, id2); + Patient patient = myPatientDao.read(new IdType(id), mySrd); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient)); + assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString()); } // @@ -2178,6 +2225,99 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { // // } + @Test + public void testTransactionWithReferenceToCreateIfNoneExist() { + Bundle bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + + Medication med = new Medication(); + IdType medId = IdType.newRandomUuid(); + med.setId(medId); + med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); + bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); + + MedicationRequest mo = new MedicationRequest(); + mo.setMedication(new Reference(medId)); + bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); + + ourLog.info("Request:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + + Bundle outcome = mySystemDao.transaction(mySrd, bundle); + ourLog.info("Response:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + + IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + /* + * Again! + */ + + bundle = new Bundle(); + bundle.setType(BundleType.TRANSACTION); + + med = new Medication(); + medId = IdType.newRandomUuid(); + med.getCode().addCoding().setSystem("billscodes").setCode("theCode"); + bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode"); + + mo = new MedicationRequest(); + mo.setMedication(new Reference(medId)); + bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST); + + outcome = mySystemDao.transaction(mySrd, bundle); + + IdType medId2 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + IdType medOrderId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + assertTrue(medId1.isIdPartValidLong()); + assertTrue(medId2.isIdPartValidLong()); + assertTrue(medOrderId1.isIdPartValidLong()); + assertTrue(medOrderId2.isIdPartValidLong()); + + assertEquals(medId1, medId2); + assertNotEquals(medOrderId1, medOrderId2); + } + + + @Test + public void testTransactionWithRelativeOidIds() throws Exception { + Bundle res = new Bundle(); + res.setType(BundleType.TRANSACTION); + + Patient p1 = new Patient(); + p1.setId("urn:oid:0.1.2.3"); + p1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds01"); + res.addEntry().setResource(p1).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient"); + + Observation o1 = new Observation(); + o1.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds02"); + o1.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Observation o2 = new Observation(); + o2.addIdentifier().setSystem("system").setValue("testTransactionWithRelativeOidIds03"); + o2.setSubject(new Reference("urn:oid:0.1.2.3")); + res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST).setUrl("Observation"); + + Bundle resp = mySystemDao.transaction(mySrd, res); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(3, resp.getEntry().size()); + + assertTrue(resp.getEntry().get(0).getResponse().getLocation(), new IdType(resp.getEntry().get(0).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(1).getResponse().getLocation(), new IdType(resp.getEntry().get(1).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + assertTrue(resp.getEntry().get(2).getResponse().getLocation(), new IdType(resp.getEntry().get(2).getResponse().getLocation()).getIdPart().matches("^[0-9]+$")); + + o1 = myObservationDao.read(new IdType(resp.getEntry().get(1).getResponse().getLocation()), mySrd); + o2 = myObservationDao.read(new IdType(resp.getEntry().get(2).getResponse().getLocation()), mySrd); + assertThat(o1.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); + + } + + /** * This is not the correct way to do it, but we'll allow it to be lenient */ @@ -2218,147 +2358,89 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart())); } - - + + /** + * See #467 + */ @Test - public void testTransactionDoubleConditionalCreateOnlyCreatesOne() { - Bundle inputBundle = new Bundle(); - inputBundle.setType(Bundle.BundleType.TRANSACTION); + public void testTransactionWithSelfReferentialLink() { + /* + * Link to each other + */ + Bundle request = new Bundle(); - Encounter enc1 = new Encounter(); - enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); - inputBundle - .addEntry() - .setResource(enc1) - .getRequest() - .setMethod(HTTPVerb.POST) - .setIfNoneExist("Encounter?identifier=urn:foo|12345"); - Encounter enc2 = new Encounter(); - enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); - inputBundle - .addEntry() - .setResource(enc2) - .getRequest() - .setMethod(HTTPVerb.POST) - .setIfNoneExist("Encounter?identifier=urn:foo|12345"); + Organization o1 = new Organization(); + o1.setId(IdType.newRandomUuid()); + o1.setName("ORG1"); + request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.POST); - try { - mySystemDao.transaction(mySrd, inputBundle); - fail(); - } catch (InvalidRequestException e) { - assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage()); - } - } - + Organization o2 = new Organization(); + o2.setName("ORG2"); + o2.setId(IdType.newRandomUuid()); + request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.POST); - @Test - public void testTransactionDoubleConditionalUpdateOnlyCreatesOne() { - Bundle inputBundle = new Bundle(); - inputBundle.setType(Bundle.BundleType.TRANSACTION); + o1.getPartOf().setReference(o2.getId()); + o2.getPartOf().setReference(o1.getId()); - Encounter enc1 = new Encounter(); - enc1.addIdentifier().setSystem("urn:foo").setValue("12345"); - inputBundle - .addEntry() - .setResource(enc1) - .getRequest() - .setMethod(HTTPVerb.PUT) - .setUrl("Encounter?identifier=urn:foo|12345"); - Encounter enc2 = new Encounter(); - enc2.addIdentifier().setSystem("urn:foo").setValue("12345"); - inputBundle - .addEntry() - .setResource(enc2) - .getRequest() - .setMethod(HTTPVerb.PUT) - .setUrl("Encounter?identifier=urn:foo|12345"); + Bundle resp = mySystemDao.transaction(mySrd, request); + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(2, resp.getEntry().size()); - try { - mySystemDao.transaction(mySrd, inputBundle); - fail(); - } catch (InvalidRequestException e) { - assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage()); - } - - } + IdType id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + IdType id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); - @Test - public void testCircularCreateAndDelete() { - Encounter enc = new Encounter(); - enc.setId(IdType.newRandomUuid()); + ourLog.info("ID1: {}", id1); - Condition cond = new Condition(); - cond.setId(IdType.newRandomUuid()); + SearchParameterMap map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); + IBundleProvider res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); - EpisodeOfCare ep = new EpisodeOfCare(); - ep.setId(IdType.newRandomUuid()); - - enc.getEpisodeOfCareFirstRep().setReference(ep.getId()); - cond.getContext().setReference(enc.getId()); - ep.getDiagnosisFirstRep().getCondition().setReference(cond.getId()); - - Bundle inputBundle = new Bundle(); - inputBundle.setType(Bundle.BundleType.TRANSACTION); - inputBundle - .addEntry() - .setResource(ep) - .setFullUrl(ep.getId()) - .getRequest().setMethod(HTTPVerb.POST); - inputBundle - .addEntry() - .setResource(cond) - .setFullUrl(cond.getId()) - .getRequest().setMethod(HTTPVerb.POST); - inputBundle - .addEntry() - .setResource(enc) - .setFullUrl(enc.getId()) - .getRequest().setMethod(HTTPVerb.POST); - - Bundle resp = mySystemDao.transaction(mySrd, inputBundle); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - IdType epId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); - IdType condId = new IdType(resp.getEntry().get(1).getResponse().getLocation()); - IdType encId = new IdType(resp.getEntry().get(2).getResponse().getLocation()); - - // Make sure a single one can't be deleted - try { - myEncounterDao.delete(encId); - fail(); - } catch (ResourceVersionConflictException e) { - // good - } + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); /* - * Now delete all 3 by transaction + * Link to self */ - inputBundle = new Bundle(); - inputBundle.setType(Bundle.BundleType.TRANSACTION); - inputBundle - .addEntry() - .getRequest().setMethod(HTTPVerb.DELETE) - .setUrl(epId.toUnqualifiedVersionless().getValue()); - inputBundle - .addEntry() - .getRequest().setMethod(HTTPVerb.DELETE) - .setUrl(encId.toUnqualifiedVersionless().getValue()); - inputBundle - .addEntry() - .getRequest().setMethod(HTTPVerb.DELETE) - .setUrl(condId.toUnqualifiedVersionless().getValue()); + request = new Bundle(); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(inputBundle)); - resp = mySystemDao.transaction(mySrd, inputBundle); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + o1 = new Organization(); + o1.setId(id1); + o1.setName("ORG1"); + request.addEntry().setResource(o1).getRequest().setMethod(HTTPVerb.PUT).setUrl(id1.toUnqualifiedVersionless().getValue()); - // They should now be deleted - try { - myEncounterDao.read(encId.toUnqualifiedVersionless()); - fail(); - } catch (ResourceGoneException e) { - // good - } + o2 = new Organization(); + o2.setName("ORG2"); + o2.setId(id2); + request.addEntry().setResource(o2).getRequest().setMethod(HTTPVerb.PUT).setUrl(id2.toUnqualifiedVersionless().getValue()); + + o1.getPartOf().setReference(o1.getId()); + o2.getPartOf().setReference(o2.getId()); + + resp = mySystemDao.transaction(mySrd, request); + assertEquals(BundleType.TRANSACTIONRESPONSE, resp.getTypeElement().getValue()); + assertEquals(2, resp.getEntry().size()); + + id1 = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + id2 = new IdType(resp.getEntry().get(1).getResponse().getLocation()); + + ourLog.info("ID1: {}", id1); + + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id1.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id1.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + map = new SearchParameterMap(); + map.add(Organization.SP_PARTOF, new ReferenceParam(id2.toUnqualifiedVersionless().getValue())); + res = myOrganizationDao.search(map); + assertEquals(1, res.size().intValue()); + assertEquals(id2.toUnqualifiedVersionless().getValue(), res.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless().getValue()); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 728f5fe8d8c..23c14116542 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -36,7 +36,15 @@ Search results will be cached and reused (so that if a client does two searches for "get me all patients matching FOO" with the same FOO in short succession, we won't query the DB - again but will instead reuse the cached results) + again but will instead reuse the cached results). Note that + this can improve performance, but does mean that searches can + return slightly out of date results. Essentially what this means + is that the latest version of individual resources will always + be returned despite this cacheing, but newly created resources + that should match may not be returned until the cache + expires. By default this cache has been set to one minute, + which should be acceptable for most real-world usage, but + this can be changed or disabled entirely.
  • Updates which do not actually change the contents of the resource @@ -50,7 +58,8 @@ HFJ_SEARCH_INCLUDE, and HFJ_SEARCH_RESULT - tables from your database before upgrading. + tables from your database before upgrading, as the structure of these tables + has changed and old search results can not be reused. ]]>