package ca.uhn.fhir.jpa.dao.dstu2; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Test; import org.mockito.ArgumentCaptor; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 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 FhirResourceDaoDstu2UpdateTest extends BaseJpaDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2UpdateTest.class); @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } @Test public void testUpdateAndGetHistoryResource() throws InterruptedException { Patient patient = new Patient(); patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("Tester").addGiven("Joe"); MethodOutcome outcome = myPatientDao.create(patient, mySrd); assertNotNull(outcome.getId()); assertFalse(outcome.getId().isEmpty()); assertEquals("1", outcome.getId().getVersionIdPart()); Date now = new Date(); Patient retrieved = myPatientDao.read(outcome.getId(), mySrd); InstantDt published = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); InstantDt updated = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); assertTrue(published.before(now)); assertTrue(updated.before(now)); Thread.sleep(1000); reset(myInterceptor); retrieved.getIdentifierFirstRep().setValue("002"); MethodOutcome outcome2 = myPatientDao.update(retrieved, mySrd); assertEquals(outcome.getId().getIdPart(), outcome2.getId().getIdPart()); assertNotEquals(outcome.getId().getVersionIdPart(), outcome2.getId().getVersionIdPart()); assertEquals("2", outcome2.getId().getVersionIdPart()); // Verify interceptor ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); verify(myInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.UPDATE), detailsCapt.capture()); ActionRequestDetails details = detailsCapt.getValue(); assertNotNull(details.getId()); assertEquals("Patient", details.getResourceType()); assertEquals(Patient.class, details.getResource().getClass()); Date now2 = new Date(); Patient retrieved2 = myPatientDao.read(outcome.getId().toVersionless(), mySrd); assertEquals("2", retrieved2.getId().getVersionIdPart()); assertEquals("002", retrieved2.getIdentifierFirstRep().getValue()); InstantDt published2 = (InstantDt) retrieved2.getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); InstantDt updated2 = (InstantDt) retrieved2.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); assertTrue(published2.before(now)); assertTrue(updated2.after(now)); assertTrue(updated2.before(now2)); Thread.sleep(2000); /* * Get history */ IBundleProvider historyBundle = myPatientDao.history(outcome.getId(), null, null, mySrd); assertEquals(2, historyBundle.size().intValue()); List<IBaseResource> history = historyBundle.getResources(0, 2); assertEquals("1", history.get(1).getIdElement().getVersionIdPart()); assertEquals("2", history.get(0).getIdElement().getVersionIdPart()); assertEquals(published, ResourceMetadataKeyEnum.PUBLISHED.get((IResource) history.get(1))); assertEquals(published, ResourceMetadataKeyEnum.PUBLISHED.get((IResource) history.get(1))); assertEquals(updated, ResourceMetadataKeyEnum.UPDATED.get((IResource) history.get(1))); assertEquals("001", ((Patient) history.get(1)).getIdentifierFirstRep().getValue()); assertEquals(published2, ResourceMetadataKeyEnum.PUBLISHED.get((IResource) history.get(0))); assertEquals(updated2, ResourceMetadataKeyEnum.UPDATED.get((IResource) history.get(0))); assertEquals("002", ((Patient) history.get(0)).getIdentifierFirstRep().getValue()); } @Test public void testUpdateByUrl() { String methodName = "testUpdateByUrl"; Patient 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().addFamily("Hello"); p.setId("Patient/" + methodName); myPatientDao.update(p, "Patient?identifier=urn%3Asystem%7C" + methodName, mySrd); p = myPatientDao.read(id.toVersionless(), mySrd); assertThat(p.getId().toVersionless().toString(), not(containsString("test"))); assertEquals(id.toVersionless(), p.getId().toVersionless()); assertNotEquals(id, p.getId()); assertThat(p.getId().toString(), endsWith("/_history/2")); } @Test public void testUpdateCreatesTextualIdIfItDoesntAlreadyExist() { Patient p = new Patient(); String methodName = "testUpdateCreatesTextualIdIfItDoesntAlreadyExist"; p.addIdentifier().setSystem("urn:system").setValue(methodName); p.addName().addFamily("Hello"); p.setId("Patient/" + methodName); IIdType id = myPatientDao.update(p, mySrd).getId(); assertEquals("Patient/" + methodName, id.toUnqualifiedVersionless().getValue()); p = myPatientDao.read(id, mySrd); assertEquals(methodName, p.getIdentifierFirstRep().getValue()); } @Test public void testUpdateDoesntFailForUnknownIdWithNumberThenText() { String methodName = "testUpdateFailsForUnknownIdWithNumberThenText"; Patient p = new Patient(); p.setId("0" + methodName); p.addName().addFamily(methodName); 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"; IIdType p1id; { Patient p1 = new Patient(); p1.addName().addFamily(methodName); TagList tagList = new TagList(); tagList.addTag("tag_scheme1", "tag_term1"); ResourceMetadataKeyEnum.TAG_LIST.put(p1, tagList); List<BaseCodingDt> secList = new ArrayList<BaseCodingDt>(); secList.add(new CodingDt("sec_scheme1", "sec_term1")); ResourceMetadataKeyEnum.SECURITY_LABELS.put(p1, secList); List<IdDt> profileList = new ArrayList<IdDt>(); profileList.add(new IdDt("http://foo1")); ResourceMetadataKeyEnum.PROFILES.put(p1, profileList); p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless(); } { Patient p1 = new Patient(); p1.setId(p1id); p1.addName().addFamily(methodName); TagList tagList = new TagList(); tagList.addTag("tag_scheme2", "tag_term2"); ResourceMetadataKeyEnum.TAG_LIST.put(p1, tagList); List<BaseCodingDt> secList = new ArrayList<BaseCodingDt>(); secList.add(new CodingDt("sec_scheme2", "sec_term2")); ResourceMetadataKeyEnum.SECURITY_LABELS.put(p1, secList); List<IdDt> profileList = new ArrayList<IdDt>(); profileList.add(new IdDt("http://foo2")); ResourceMetadataKeyEnum.PROFILES.put(p1, profileList); myPatientDao.update(p1, mySrd); } { Patient p1 = myPatientDao.read(p1id, mySrd); TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(p1); assertThat(tagList, containsInAnyOrder(new Tag("tag_scheme1", "tag_term1"), new Tag("tag_scheme2", "tag_term2"))); List<BaseCodingDt> secList = ResourceMetadataKeyEnum.SECURITY_LABELS.get(p1); Set<String> secListValues = new HashSet<String>(); for (BaseCodingDt next : secList) { secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); } assertThat(secListValues, containsInAnyOrder("sec_scheme1|sec_term1", "sec_scheme2|sec_term2")); List<IdDt> profileList = ResourceMetadataKeyEnum.PROFILES.get(p1); assertThat(profileList, contains(new IdDt("http://foo2"))); // no foo1 } } @Test public void testUpdateMaintainsSearchParams() throws InterruptedException { Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2AAA"); p1.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2AAA"); IIdType p1id = myPatientDao.create(p1, mySrd).getId(); Patient p2 = new Patient(); p2.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2BBB"); p2.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB"); myPatientDao.create(p2, mySrd).getId(); Set<Long> ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsDstu2AAA"))); assertEquals(1, ids.size()); assertThat(ids, contains(p1id.getIdPartAsLong())); // Update the name p1.getNameFirstRep().getGivenFirstRep().setValue("testUpdateMaintainsSearchParamsDstu2BBB"); MethodOutcome update2 = myPatientDao.update(p1, mySrd); IIdType p1id2 = update2.getId(); ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsDstu2AAA"))); assertEquals(0, ids.size()); ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsDstu2BBB"))); assertEquals(2, ids.size()); // Make sure vreads work p1 = myPatientDao.read(p1id, mySrd); assertEquals("testUpdateMaintainsSearchParamsDstu2AAA", p1.getNameFirstRep().getGivenAsSingleString()); p1 = myPatientDao.read(p1id2, mySrd); assertEquals("testUpdateMaintainsSearchParamsDstu2BBB", p1.getNameFirstRep().getGivenAsSingleString()); } @Test public void testUpdateRejectsInvalidTypes() throws InterruptedException { Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes"); p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes"); IIdType p1id = myPatientDao.create(p1, mySrd).getId(); Organization p2 = new Organization(); p2.getNameElement().setValue("testUpdateRejectsInvalidTypes"); try { p2.setId(new IdDt("Organization/" + p1id.getIdPart())); myOrganizationDao.update(p2, mySrd); fail(); } catch (UnprocessableEntityException e) { // good } try { p2.setId(new IdDt("Patient/" + p1id.getIdPart())); myOrganizationDao.update(p2, mySrd); fail(); } catch (UnprocessableEntityException e) { ourLog.error("Good", e); } } @Test public void testDuplicateProfilesIgnored() { String name = "testDuplicateProfilesIgnored"; IIdType id; { Patient patient = new Patient(); patient.addName().addFamily(name); List<IdDt> tl = new ArrayList<IdDt>(); tl.add(new IdDt("http://foo/bar")); tl.add(new IdDt("http://foo/bar")); tl.add(new IdDt("http://foo/bar")); ResourceMetadataKeyEnum.PROFILES.put(patient, tl); id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); } // Do a read { Patient patient = myPatientDao.read(id, mySrd); List<IdDt> tl = ResourceMetadataKeyEnum.PROFILES.get(patient); assertEquals(1, tl.size()); assertEquals("http://foo/bar", tl.get(0).getValue()); } } @Test public void testUpdateModifiesProfiles() { String name = "testUpdateModifiesProfiles"; IIdType id; { Patient patient = new Patient(); patient.addName().addFamily(name); List<IdDt> tl = new ArrayList<IdDt>(); tl.add(new IdDt("http://foo/bar")); ResourceMetadataKeyEnum.PROFILES.put(patient, tl); id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); } // Do a read { Patient patient = myPatientDao.read(id, mySrd); List<IdDt> tl = ResourceMetadataKeyEnum.PROFILES.get(patient); assertEquals(1, tl.size()); assertEquals("http://foo/bar", tl.get(0).getValue()); } // Update { Patient patient = new Patient(); patient.setId(id); patient.addName().addFamily(name); List<IdDt> tl = new ArrayList<IdDt>(); tl.add(new IdDt("http://foo/baz")); ResourceMetadataKeyEnum.PROFILES.put(patient, tl); id = myPatientDao.update(patient, mySrd).getId().toUnqualifiedVersionless(); } // Do a read { Patient patient = myPatientDao.read(id, mySrd); List<IdDt> tl = ResourceMetadataKeyEnum.PROFILES.get(patient); assertEquals(1, tl.size()); assertEquals("http://foo/baz", tl.get(0).getValue()); } } @Test public void testUpdateUnknownNumericIdFails() { Patient p = new Patient(); p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); p.addName().addFamily("Hello"); p.setId("Patient/9999999999999999"); try { myPatientDao.update(p, mySrd); fail(); } catch (InvalidRequestException e) { assertThat(e.getMessage(), containsString("Can not create resource with ID[9999999999999999], no resource with this ID exists and clients may only")); } } @Test public void testUpdateWithInvalidIdFails() { Patient p = new Patient(); p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); p.addName().addFamily("Hello"); p.setId("Patient/123:456"); try { myPatientDao.update(p, mySrd); fail(); } catch (InvalidRequestException e) { assertEquals("Can not process entity with ID[123:456], this is not a valid FHIR ID", e.getMessage()); } } @Test public void testUpdateWithNumericIdFails() { Patient p = new Patient(); p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails"); p.addName().addFamily("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().addFamily("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.getId().toUnqualifiedVersionless().getValue()); assertEquals("Hello", p.getName().get(0).getFamily().get(0).getValue()); } }