From fac14240c8526b7c603124969af562c204090fd4 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Mon, 27 Jul 2020 14:18:40 -0700 Subject: [PATCH] Add circular reference handling, and DeleteConflict handling --- .../uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java | 1 + .../fhir/jpa/empi/svc/EmpiResetSvcImpl.java | 54 ++++++++++++++++++- .../ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java | 7 +++ .../provider/EmpiProviderClearLinkR4Test.java | 44 +++++++++++++-- 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java index b2d4260df9c..bb71ce505fb 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java @@ -231,6 +231,7 @@ public class EmpiLinkDaoSvc { * * @return A list of Long representing the related Person Pids. */ + @Transactional public List deleteAllEmpiLinksAndReturnPersonPids() { List all = myEmpiLinkDao.findAll(); return deleteEmpiLinksAndReturnPersonPids(all); diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResetSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResetSvcImpl.java index 16b0ae4b224..de94f2e4de8 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResetSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResetSvcImpl.java @@ -22,10 +22,19 @@ package ca.uhn.fhir.jpa.empi.svc; import ca.uhn.fhir.empi.api.IEmpiResetSvc; import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.expunge.IResourceExpungeService; +import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.IdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -47,9 +56,18 @@ public class EmpiResetSvcImpl implements IEmpiResetSvc { @Autowired private IResourceExpungeService myResourceExpungeService; + @Autowired + private IResourceTableDao myResourceTable; + + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private HapiTransactionService myTransactionService; + @Override public long expungeAllEmpiLinksOfTargetType(String theResourceType) { throwExceptionIfInvalidTargetType(theResourceType); + List longs = myEmpiLinkDaoSvc.deleteAllEmpiLinksOfTypeAndReturnPersonPids(theResourceType); myResourceExpungeService.expungeCurrentVersionOfResources(null, longs, new AtomicInteger(longs.size())); return longs.size(); @@ -69,9 +87,43 @@ public class EmpiResetSvcImpl implements IEmpiResetSvc { List longs = myEmpiLinkDaoSvc.deleteAllEmpiLinksAndReturnPersonPids(); longs = longs.stream() .distinct().collect(Collectors.toList()); + + DeleteConflictList dlc = new DeleteConflictList(); + List finalLongs = longs; + myTransactionService.execute(null, tx -> { + finalLongs.stream().forEach(pid -> { + deleteCascade(pid, dlc); + }); + return null; + }); + + IFhirResourceDao personDao = myDaoRegistry.getResourceDao("Person"); + while (!dlc.isEmpty()) { + myTransactionService.execute(null, tx -> { + deleteConflictBatch(dlc, personDao); + return null; + }); + } myResourceExpungeService.expungeHistoricalVersionsOfIds(null, longs, new AtomicInteger(longs.size())); - myResourceExpungeService.expungeCurrentVersionOfResources(null, longs, new AtomicInteger(longs.size())); + //myResourceExpungeService.expungeCurrentVersionOfResources(null, longs, new AtomicInteger(longs.size())); return longs.size(); } + + private void deleteConflictBatch(DeleteConflictList theDcl, IFhirResourceDao theDao) { + DeleteConflictList newBatch = new DeleteConflictList(); + for (DeleteConflict next: theDcl) { + IdDt nextSource = next.getSourceId(); + ourLog.info("Have delete conflict {} - Cascading delete", next); + theDao.delete(nextSource.toVersionless(), newBatch, null, null); + } + theDcl.removeIf(x -> true); + theDcl.addAll(newBatch); + } + + private void deleteCascade(Long pid, DeleteConflictList theDcl) { + ourLog.debug("About to cascade delete: " + pid); + IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao("Person"); + resourceDao.delete(new IdType("Person/" + pid), theDcl, null, null); + } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java index d616309fb05..7ecd67a1cfa 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java @@ -73,6 +73,8 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test { private static final ContactPoint TEST_TELECOM = new ContactPoint() .setSystem(ContactPoint.ContactPointSystem.PHONE) .setValue("555-555-5555"); + private static final String NAME_GIVEN_FRANK = "Frank"; + protected static final String FRANK_ID = "ID.FRANK.789"; @Autowired protected FhirContext myFhirContext; @@ -244,6 +246,11 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test { return buildPatientWithNameAndId(NAME_GIVEN_PAUL, PAUL_ID); } + @Nonnull + protected Patient buildFrankPatient() { + return buildPatientWithNameAndId(NAME_GIVEN_FRANK, FRANK_ID); + } + @Nonnull protected Patient buildJaneWithBirthday(Date theToday) { return buildPatientWithNameIdAndBirthday(NAME_GIVEN_JANE, JANE_ID, theToday); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderClearLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderClearLinkR4Test.java index 49d6c310bc3..c19b8314679 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderClearLinkR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderClearLinkR4Test.java @@ -1,7 +1,9 @@ package ca.uhn.fhir.jpa.empi.provider; import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.r4.model.Patient; @@ -92,14 +94,46 @@ public class EmpiProviderClearLinkR4Test extends BaseLinkR4Test { Person personFromTarget = getPersonFromTarget(patientAndUpdateLinks); Person personFromTarget2 = getPersonFromTarget(patientAndUpdateLinks1); - Person.PersonLinkComponent plc = new Person.PersonLinkComponent(); - plc.setAssurance(Person.IdentityAssuranceLevel.LEVEL2); - plc.setTarget(new Reference(personFromTarget2.getIdElement().toUnqualifiedVersionless())); - personFromTarget.getLink().add(plc); - myPersonDao.update(personFromTarget); + linkPersons(personFromTarget, personFromTarget2); + //SUT myEmpiProviderR4.clearEmpiLinks(null); + assertNoPatientLinksExist(); + IBundleProvider search = myPersonDao.search(new SearchParameterMap().setLoadSynchronous(true)); + assertThat(search.size(), is(equalTo(0))); + } + + @Test + public void testPersonsWithCircularReferenceCanBeCleared() { + Patient patientAndUpdateLinks = createPatientAndUpdateLinks(buildPaulPatient()); + Patient patientAndUpdateLinks1 = createPatientAndUpdateLinks(buildJanePatient()); + Patient patientAndUpdateLinks2 = createPatientAndUpdateLinks(buildFrankPatient()); + + Person personFromTarget = getPersonFromTarget(patientAndUpdateLinks); + Person personFromTarget1 = getPersonFromTarget(patientAndUpdateLinks1); + Person personFromTarget2 = getPersonFromTarget(patientAndUpdateLinks2); + + // A -> B -> C -> A linkages. + linkPersons(personFromTarget, personFromTarget1); + linkPersons(personFromTarget1, personFromTarget2); + linkPersons(personFromTarget2, personFromTarget); + + //SUT + myEmpiProviderR4.clearEmpiLinks(null); + + assertNoPatientLinksExist(); + IBundleProvider search = myPersonDao.search(new SearchParameterMap().setLoadSynchronous(true)); + assertThat(search.size(), is(equalTo(0))); + + } + + private void linkPersons(Person theSourcePerson, Person theTargetPerson) { + Person.PersonLinkComponent plc1 = new Person.PersonLinkComponent(); + plc1.setAssurance(Person.IdentityAssuranceLevel.LEVEL2); + plc1.setTarget(new Reference(theTargetPerson.getIdElement().toUnqualifiedVersionless())); + theSourcePerson.getLink().add(plc1); + myPersonDao.update(theSourcePerson); } @Test