package ca.uhn.fhir.jpa.dao.dstu3; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Date; import java.util.List; import javax.persistence.TypedQuery; import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SubscriptionTest.class); @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } @Autowired private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao; @Autowired private ISubscriptionTableDao mySubscriptionTableDao; @Before public void beforeEnableSubscription() { myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(60); } @Test public void testSubscriptionGetsPurgedIfItIsNeverActive() throws Exception { myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1); Subscription subs = new Subscription(); subs.setCriteria("Observation?subject=Patient/123"); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setStatus(SubscriptionStatus.REQUESTED); IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); mySubscriptionDao.purgeInactiveSubscriptions(); mySubscriptionDao.read(id, mySrd); Thread.sleep(1500); myDaoConfig.setSchedulingDisabled(false); mySubscriptionDao.purgeInactiveSubscriptions(); try { mySubscriptionDao.read(id, mySrd); fail(); } catch (ResourceGoneException e) { // good } } @Before public void beforeDisableScheduling() { myDaoConfig.setSchedulingDisabled(true); } @Test public void testSubscriptionGetsPurgedIfItIsInactive() throws Exception { myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1); Subscription subs = new Subscription(); subs.setCriteria("Observation?subject=Patient/123"); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setStatus(SubscriptionStatus.REQUESTED); IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); mySubscriptionDao.purgeInactiveSubscriptions(); mySubscriptionDao.read(id, mySrd); mySubscriptionDao.getUndeliveredResourcesAndPurge(mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id)); Thread.sleep(1500); myDaoConfig.setSchedulingDisabled(false); mySubscriptionDao.purgeInactiveSubscriptions(); try { mySubscriptionDao.read(id, mySrd); fail(); } catch (ResourceGoneException e) { // good } } @Test public void testCreateSubscription() { Subscription subs = new Subscription(); subs.setCriteria("Observation?subject=Patient/123"); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setStatus(SubscriptionStatus.REQUESTED); IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); TypedQuery<SubscriptionTable> q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class); q.setParameter("id", id.getIdPartAsLong()); final SubscriptionTable table = q.getSingleResult(); assertNotNull(table); assertNotNull(table.getNextCheck()); assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue()); assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus()); assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); subs.setStatus(SubscriptionStatus.ACTIVE); mySubscriptionDao.update(subs, mySrd); assertEquals(SubscriptionStatus.ACTIVE.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus()); assertEquals(SubscriptionStatus.ACTIVE, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); mySubscriptionDao.delete(id, mySrd); assertNull(myEntityManager.find(SubscriptionTable.class, table.getId())); /* * Re-create again */ subs = new Subscription(); subs.setCriteria("Observation?subject=Patient/123"); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setId(id); subs.setStatus(SubscriptionStatus.REQUESTED); mySubscriptionDao.update(subs, mySrd); assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus()); assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue()); } @Test public void testCreateSubscriptionInvalidCriteria() { Subscription subs = new Subscription(); subs.setStatus(SubscriptionStatus.REQUESTED); subs.setCriteria("Observation"); try { mySubscriptionDao.create(subs, mySrd); fail(); } catch (UnprocessableEntityException e) { assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\"")); } subs = new Subscription(); subs.setStatus(SubscriptionStatus.REQUESTED); subs.setCriteria("http://foo.com/Observation?AAA=BBB"); try { mySubscriptionDao.create(subs, mySrd); fail(); } catch (UnprocessableEntityException e) { assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\"")); } subs = new Subscription(); subs.setStatus(SubscriptionStatus.REQUESTED); subs.setCriteria("ObservationZZZZ?a=b"); try { mySubscriptionDao.create(subs, mySrd); fail(); } catch (UnprocessableEntityException e) { assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ")); } subs = new Subscription(); subs.setStatus(SubscriptionStatus.REQUESTED); subs.setCriteria("Observation?identifier=123"); try { mySubscriptionDao.create(subs, mySrd); fail(); } catch (UnprocessableEntityException e) { assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated on this server")); } subs = new Subscription(); subs.setStatus(SubscriptionStatus.REQUESTED); subs.setCriteria("Observation?identifier=123"); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart()); } @Test public void testDeleteSubscriptionWithFlaggedResources() throws Exception { myDaoConfig.setSubscriptionPollDelay(0); String methodName = "testDeleteSubscriptionWithFlaggedResources"; Patient p = new Patient(); p.addName().setFamily(methodName); IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); Subscription subs; /* * Create 2 identical subscriptions */ subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); subs.setStatus(SubscriptionStatus.ACTIVE); IIdType subsId = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless(); Long subsPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(subsId); assertNull(mySubscriptionTableDao.findOne(subsPid).getLastClientPoll()); Thread.sleep(100); ourLog.info("Before: {}", System.currentTimeMillis()); assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); assertThat(mySubscriptionTableDao.count(), equalTo(1L)); Observation obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Thread.sleep(100); ourLog.info("After: {}", System.currentTimeMillis()); mySubscriptionDao.pollForNewUndeliveredResources(); assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L)); assertThat(mySubscriptionTableDao.count(), greaterThan(0L)); /* * Delete the subscription */ mySubscriptionDao.delete(subsId, mySrd); assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); assertThat(mySubscriptionTableDao.count(), not(greaterThan(0L))); /* * Delete a second time just to make sure that works */ mySubscriptionDao.delete(subsId, mySrd); /* * Re-create the subscription */ subs.setId(subsId); mySubscriptionDao.update(subs, mySrd).getId(); assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L))); assertThat(mySubscriptionTableDao.count(), (greaterThan(0L))); /* * Create another resource and make sure it gets flagged */ obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Thread.sleep(100); mySubscriptionDao.pollForNewUndeliveredResources(); assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L)); assertThat(mySubscriptionTableDao.count(), greaterThan(0L)); } @Test public void testSubscriptionResourcesAppear() throws Exception { myDaoConfig.setSubscriptionPollDelay(0); String methodName = "testSubscriptionResourcesAppear"; Patient p = new Patient(); p.addName().setFamily(methodName); IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Subscription subs; /* * Create 2 identical subscriptions */ subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); subs.setStatus(SubscriptionStatus.ACTIVE); Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); subs.setStatus(SubscriptionStatus.ACTIVE); Long subsId2 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); Thread.sleep(100); ourLog.info("Before: {}", System.currentTimeMillis()); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Thread.sleep(100); ourLog.info("After: {}", System.currentTimeMillis()); List<IBaseResource> results; List<IIdType> resultIds; assertEquals(4, mySubscriptionDao.pollForNewUndeliveredResources()); assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1); resultIds = toUnqualifiedVersionlessIds(results); assertThat(resultIds, contains(afterId1, afterId2)); Date lastClientPoll = mySubscriptionTableDao.findOne(subsId1).getLastClientPoll(); assertNotNull(lastClientPoll); mySubscriptionDao.pollForNewUndeliveredResources(); results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2); resultIds = toUnqualifiedVersionlessIds(results); assertThat(resultIds, contains(afterId1, afterId2)); mySubscriptionDao.pollForNewUndeliveredResources(); results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1); resultIds = toUnqualifiedVersionlessIds(results); assertThat(resultIds, empty()); assertNotEquals(lastClientPoll, mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); mySubscriptionDao.pollForNewUndeliveredResources(); results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2); resultIds = toUnqualifiedVersionlessIds(results); assertThat(resultIds, empty()); /* * Make sure that reindexing doesn't trigger */ mySystemDao.markAllResourcesForReindexing(); mySystemDao.performReindexingPass(100); assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); /* * Update resources on disk */ IBundleProvider allObs = myObservationDao.search(new SearchParameterMap()); ourLog.info("Updating {} observations", allObs.size()); for (IBaseResource next : allObs.getResources(0, allObs.size())) { ourLog.info("Updating observation"); Observation nextObs = (Observation) next; nextObs.addPerformer().setDisplay("Some display"); myObservationDao.update(nextObs, mySrd); } assertEquals(6, mySubscriptionDao.pollForNewUndeliveredResources()); assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); } @Test public void testSubscriptionResourcesAppear2() throws Exception { myDaoConfig.setSubscriptionPollDelay(0); String methodName = "testSubscriptionResourcesAppear2"; Patient p = new Patient(); p.addName().setFamily(methodName); IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); Observation obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); IIdType oId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Subscription subs; /* * Create 2 identical subscriptions */ subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); subs.setStatus(SubscriptionStatus.ACTIVE); Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId()); assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll()); assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); ourLog.info("pId: {} - oId: {}", pId, oId); myObservationDao.update(myObservationDao.read(oId, mySrd), mySrd); assertEquals(1, mySubscriptionDao.pollForNewUndeliveredResources()); ourLog.info("Between passes"); assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources()); Thread.sleep(100); ourLog.info("Before: {}", System.currentTimeMillis()); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Thread.sleep(100); ourLog.info("After: {}", System.currentTimeMillis()); assertEquals(2, mySubscriptionDao.pollForNewUndeliveredResources()); assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); Thread.sleep(100); mySubscriptionDao.pollForNewUndeliveredResources(); assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); Thread.sleep(100); mySubscriptionDao.pollForNewUndeliveredResources(); assertEquals(3, mySubscriptionFlaggedResourceDataDao.count()); Thread.sleep(100); obs = new Observation(); obs.getSubject().setReferenceElement(pId); obs.setStatus(ObservationStatus.FINAL); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); mySubscriptionDao.pollForNewUndeliveredResources(); assertEquals(4, mySubscriptionFlaggedResourceDataDao.count()); Thread.sleep(100); mySubscriptionDao.pollForNewUndeliveredResources(); assertEquals(4, mySubscriptionFlaggedResourceDataDao.count()); } }