Make sure we don't update unchanged resources in a transaction

This commit is contained in:
James 2017-05-20 17:55:09 -04:00
parent d550392047
commit 20c14fe8a6
7 changed files with 889 additions and 680 deletions

View File

@ -1508,6 +1508,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (theResource != null) {
populateResourceIdFromEntity(theEntity, theResource);
}
theEntity.setUnchangedInCurrentOperation(true);
return theEntity;
}

View File

@ -1127,7 +1127,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> 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()));
}

View File

@ -242,6 +242,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ResourceTag> 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;
}

View File

@ -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<Coding>) retrieved.getMeta().getSecurity();
secLabels = retrieved.getMeta().getSecurity();
sortCodings(secLabels);
assertEquals(2, secLabels.size());
assertEquals("seclabel:sys:1", secLabels.get(0).getSystemElement().getValue());

View File

@ -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<IdType> tl = new ArrayList<IdType>();
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<UriType> 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<IBaseResource> 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<Coding> tagList = p1.getMeta().getTag();
Set<String> secListValues = new HashSet<String>();
for (Coding next : tagList) {
secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue());
}
assertThat(secListValues, containsInAnyOrder("tag_scheme1|tag_term1", "tag_scheme2|tag_term2"));
List<Coding> secList = p1.getMeta().getSecurity();
secListValues = new HashSet<String>();
for (Coding next : secList) {
secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue());
}
assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2"));
List<UriType> 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<IdType> tl = new ArrayList<IdType>();
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<UriType> 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<Coding> tagList = p1.getMeta().getTag();
Set<String> secListValues = new HashSet<String>();
for (Coding next : tagList) {
secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue());
}
assertThat(secListValues, containsInAnyOrder("tag_scheme1|tag_term1", "tag_scheme2|tag_term2"));
List<Coding> secList = p1.getMeta().getSecurity();
secListValues = new HashSet<String>();
for (Coding next : secList) {
secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue());
}
assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2"));
List<UriType> 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();
}
}

View File

@ -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.
</li>
<li>
Updates which do not actually change the contents of the resource
@ -50,7 +58,8 @@
<code>HFJ_SEARCH_INCLUDE</code>,
and
<code>HFJ_SEARCH_RESULT</code>
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.
]]>
</action>
<action type="fix" issue="590">