diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java index cd64a83c289..0f39c4e12f8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java @@ -27,8 +27,9 @@ import ca.uhn.fhir.empi.model.EmpiTransactionContext; import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.entity.EmpiTargetType; +import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Patient; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; @@ -42,6 +43,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service public class EmpiLinkDaoSvc { @@ -183,6 +185,26 @@ public class EmpiLinkDaoSvc { return myEmpiLinkDao.findAll(example); } + public List deleteAllEmpiLinksAndReturnPersonPids() { + List all = myEmpiLinkDao.findAll(); + return deleteEmpiLinksAndReturnPersonPids(all); + } + + private List deleteEmpiLinksAndReturnPersonPids(List theLinks) { + List collect = theLinks.stream().map(link -> link.getPersonPid()).collect(Collectors.toList()); + myEmpiLinkDao.deleteAll(); + return collect; + } + + public List deleteAllEmpiLinksOfTypeAndReturnPersonPids(EmpiTargetType theTargetType) { + EmpiLink link = new EmpiLink(); + link.setEmpiTargetType(theTargetType); + Example exampleLink = Example.of(link); + List allOfType = myEmpiLinkDao.findAll(exampleLink); + return deleteEmpiLinksAndReturnPersonPids(allOfType); + + } + public EmpiLink save(EmpiLink theEmpiLink) { if (theEmpiLink.getCreated() == null) { theEmpiLink.setCreated(new Date()); @@ -195,7 +217,7 @@ public class EmpiLinkDaoSvc { return myEmpiLinkDao.findAll(theExampleLink); } - public List findEmpiLinksByTarget(Patient theTargetResource) { + public List findEmpiLinksByTarget(IAnyResource theTargetResource) { Long pid = myIdHelperService.getPidOrNull(theTargetResource); if (pid == null) { return Collections.emptyList(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index 4a6754f5a66..a8c6c962887 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -37,13 +37,10 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.checkerframework.checker.nullness.qual.NonNull; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -55,7 +52,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -66,7 +62,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -481,7 +476,7 @@ public class IdHelperService { } public Map getPidToIdMap(Collection theIds, RequestDetails theRequestDetails) { - return theIds.stream().collect(Collectors.toMap(t->getPidOrThrowException(t), Function.identity())); + return theIds.stream().collect(Collectors.toMap(this::getPidOrThrowException, Function.identity())); } public IIdType resourceIdFromPidOrThrowException(Long thePid) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java index a194026e18a..383c1548bf8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java @@ -88,6 +88,12 @@ public class EmpiLink { @Column(name = "UPDATED", nullable = false) private Date myUpdated; + @Column(name = "TARGET_TYPE", nullable = true) + private EmpiTargetType myEmpiTargetType; + + @Column(name = "SCORE", nullable = true) + private Double myScore; + public Long getId() { return myId; } @@ -205,4 +211,20 @@ public class EmpiLink { myUpdated = theUpdated; return this; } + + public EmpiTargetType getEmpiTargetType() { + return myEmpiTargetType; + } + + public void setEmpiTargetType(EmpiTargetType theEmpiTargetType) { + myEmpiTargetType = theEmpiTargetType; + } + + public Double getScore() { + return myScore; + } + + public void setScore(Double theScore) { + myScore = theScore; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiTargetType.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiTargetType.java new file mode 100644 index 00000000000..f352ead3753 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiTargetType.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.jpa.entity; + +public enum EmpiTargetType { + PATIENT, + PRACTITIONER +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java index 6ae308bb772..e630697c8f8 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.empi.config; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; import ca.uhn.fhir.empi.api.IEmpiLinkSvc; import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; @@ -40,6 +41,7 @@ import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor; import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchCriteriaBuilderSvc; import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchSvc; import ca.uhn.fhir.jpa.empi.svc.EmpiEidUpdateService; +import ca.uhn.fhir.jpa.empi.svc.EmpiExpungeSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiLinkQuerySvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl; @@ -153,6 +155,11 @@ public class EmpiConsumerConfig { return new EmpiLinkQuerySvcImpl(); } + @Bean + IEmpiExpungeSvc empiExpungeSvc() { + return new EmpiExpungeSvcImpl(); + } + @Bean EmpiCandidateSearchSvc empiCandidateSearchSvc() { return new EmpiCandidateSearchSvc(); diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiExpungeSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiExpungeSvcImpl.java new file mode 100644 index 00000000000..5920977500e --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiExpungeSvcImpl.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.expunge.IResourceExpungeService; +import ca.uhn.fhir.jpa.entity.EmpiTargetType; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This class is in charge of Clearing out existing EMPI links, as well as deleting all persons related to those EMPI Links. + * + */ +public class EmpiExpungeSvcImpl implements IEmpiExpungeSvc { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiExpungeSvcImpl.class); + + @Autowired + EmpiLinkDaoSvc myEmpiLinkDaoSvc; + + @Autowired + private IResourceExpungeService myResourceExpungeService; + + @Override + public void expungeEmpiLinks(String theResourceType) { + EmpiTargetType targetType = getTargetTypeOrThrowException(theResourceType); + List longs = myEmpiLinkDaoSvc.deleteAllEmpiLinksOfTypeAndReturnPersonPids(targetType); + myResourceExpungeService.expungeHistoricalVersionsOfIds(null, longs, new AtomicInteger(longs.size())); + } + + private EmpiTargetType getTargetTypeOrThrowException(String theResourceType) { + if (theResourceType.equalsIgnoreCase("Patient")) { + return EmpiTargetType.PATIENT; + } else if(theResourceType.equalsIgnoreCase("Practitioner")) { + return EmpiTargetType.PRACTITIONER; + } else { + throw new InvalidRequestException(ProviderConstants.EMPI_CLEAR + " does not support resource type: " + theResourceType); + } + } + + @Override + public void expungeEmpiLinks() { + List longs = myEmpiLinkDaoSvc.deleteAllEmpiLinksAndReturnPersonPids(); + myResourceExpungeService.expungeHistoricalVersionsOfIds(null, longs, new AtomicInteger(longs.size()));; + } +} + diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/batch/EmpiBatchTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/batch/EmpiBatchTest.java deleted file mode 100644 index 28a1d2e85bd..00000000000 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/batch/EmpiBatchTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package ca.uhn.fhir.jpa.empi.batch; - -import org.junit.Test; - -public class EmpiBatchTest { - - @Test - public void testEmpiBatch() { - - } -} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java index 38a610678f7..0b3d4a35013 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java @@ -50,8 +50,10 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test { return myEmpiLinkDaoSvc.findEmpiLinkByTarget(myPatient).get(); } + @Nonnull protected List getPatientLinks() { return myEmpiLinkDaoSvc.findEmpiLinksByTarget(myPatient); } + } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java index ef4e72d79bc..da21e9ec848 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.empi.provider; +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; @@ -21,11 +22,13 @@ public abstract class BaseProviderR4Test extends BaseEmpiR4Test { private IEmpiLinkQuerySvc myEmpiLinkQuerySvc; @Autowired private IResourceLoader myResourceLoader; + @Autowired + private IEmpiExpungeSvc myEmpiExpungeSvc; EmpiProviderR4 myEmpiProviderR4; @Before public void before() { - myEmpiProviderR4 = new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader); + myEmpiProviderR4 = new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader, myEmpiExpungeSvc); } } 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 new file mode 100644 index 00000000000..5f84cbd78d1 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderClearLinkR4Test.java @@ -0,0 +1,173 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Before; +import org.junit.Test; + +import javax.annotation.Nonnull; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class EmpiProviderClearLinkR4Test extends BaseLinkR4Test { + + + protected Practitioner myPractitioner; + protected StringType myPractitionerId; + + @Before + public void before() { + super.before(); + myPractitioner = createPractitionerAndUpdateLinks(new Practitioner()); + myPractitionerId = new StringType(myPractitioner.getIdElement().getValue()); + } + + @Test + public void testClearAllLinks() { + assertLinkCount(2); + myEmpiProviderR4.clearEmpiLinks(null); + assertLinkCount(0); + assertNoLinksExist(); + } + + private void assertNoLinksExist() { + assertNoPatientLinksExist(); + assertNoPractitionerLinksExist(); + } + + private void assertNoPatientLinksExist() { + assertThat(getPatientLinks(), hasSize(0)); + } + + private void assertNoPractitionerLinksExist() { + assertThat(getPractitionerLinks(), hasSize(0)); + } + + @Test + public void testClearPatientLinks() { + assertLinkCount(1); + myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails); + assertLinkCount(1); + + List links = getPatientLinks(); + assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + assertEquals(EmpiMatchResultEnum.MATCH, links.get(0).getMatchResult()); + } + + @Test + public void testClearPractitionerLinks() { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails); + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage(), matchesPattern("Requested resource Person/\\d+/_history/1 is not the latest version. Latest version is Person/\\d+/_history/2")); + } + } + + @Test + public void testClearInvalidTargetType() { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails); + Person person = myEmpiProviderR4.updateLink(myVersionlessPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails); + assertThat(person.getLink(), hasSize(0)); + } + + @Test + public void testUnlinkLink() { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails); + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage(), matchesPattern("Requested resource Person/\\d+/_history/1 is not the latest version. Latest version is Person/\\d+/_history/2")); + } + } + + @Test + public void testUpdateIllegalResultPM() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, POSSIBLE_MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_MATCH'. Must be NO_MATCH or MATCH", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalResultPD() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, POSSIBLE_DUPLICATE_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_DUPLICATE'. Must be NO_MATCH or MATCH", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalFirstArg() { + try { + myEmpiProviderR4.updateLink(myPatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personId must have form Person/ where is the id of the person", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalSecondArg() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPersonId, NO_MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), endsWith("must have form Patient/ or Practitioner/ where is the id of the resource")); + } + } + + @Test + public void testUpdateStrangePerson() { + Person person = createUnmanagedPerson(); + try { + myEmpiProviderR4.updateLink(new StringType(person.getIdElement().getValue()), myPatientId, NO_MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi", e.getMessage()); + } + } + + @Test + public void testExcludedPerson() { + Patient patient = new Patient(); + patient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_NO_EMPI_MANAGED); + createPatient(patient); + try { + myEmpiProviderR4.updateLink(myPersonId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked.", e.getMessage()); + } + } + + @Nonnull + protected EmpiLink getOnlyPractitionerLink() { + return myEmpiLinkDaoSvc.findEmpiLinkByTarget(myPractitioner).get(); + } + + @Nonnull + protected List getPractitionerLinks() { + return myEmpiLinkDaoSvc.findEmpiLinksByTarget(myPractitioner); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiExpungeSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiExpungeSvc.java new file mode 100644 index 00000000000..af3f8205c19 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiExpungeSvc.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +public interface IEmpiExpungeSvc { + + void expungeEmpiLinks(String theResourceType); + + void expungeEmpiLinks(); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java index 778ead37836..ff6be46de8f 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java @@ -28,4 +28,5 @@ public interface IEmpiLinkQuerySvc { IBaseParameters queryLinks(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiContext); IBaseParameters getPossibleDuplicates(EmpiTransactionContext theEmpiContext); + } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java index 5a4a02ce9bd..4fea65a3ac6 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.empi.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; @@ -52,6 +53,7 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider { private final IEmpiPersonMergerSvc myPersonMergerSvc; private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + private final IEmpiExpungeSvc myEmpiExpungeSvc; /** * Constructor @@ -59,12 +61,13 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider { * Note that this is not a spring bean. Any necessary injections should * happen in the constructor */ - public EmpiProviderDstu3(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader) { + public EmpiProviderDstu3(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader, IEmpiExpungeSvc theEmpiExpungeSvc) { super(theFhirContext, theResourceLoader); myEmpiMatchFinderSvc = theEmpiMatchFinderSvc; myPersonMergerSvc = thePersonMergerSvc; myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc; myEmpiLinkQuerySvc = theEmpiLinkQuerySvc; + myEmpiExpungeSvc = theEmpiExpungeSvc; } @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java index 81a4f90ee46..72525de3828 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.empi.provider; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; @@ -47,14 +48,16 @@ public class EmpiProviderLoader { private IEmpiLinkQuerySvc myEmpiLinkQuerySvc; @Autowired private IResourceLoader myResourceLoader; + @Autowired + private IEmpiExpungeSvc myEmpiExpungeSvc; public void loadProvider() { switch (myFhirContext.getVersion().getVersion()) { case DSTU3: - myResourceProviderFactory.addSupplier(() -> new EmpiProviderDstu3(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader)); + myResourceProviderFactory.addSupplier(() -> new EmpiProviderDstu3(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader, myEmpiExpungeSvc)); break; case R4: - myResourceProviderFactory.addSupplier(() -> new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader)); + myResourceProviderFactory.addSupplier(() -> new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader, myEmpiExpungeSvc)); break; default: throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion()); diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java index ca887f32f37..db59d89ffcb 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.empi.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiExpungeSvc; import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; @@ -52,6 +53,7 @@ public class EmpiProviderR4 extends BaseEmpiProvider { private final IEmpiPersonMergerSvc myPersonMergerSvc; private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + private final IEmpiExpungeSvc myEmpiExpungeSvc; /** * Constructor @@ -59,12 +61,13 @@ public class EmpiProviderR4 extends BaseEmpiProvider { * Note that this is not a spring bean. Any necessary injections should * happen in the constructor */ - public EmpiProviderR4(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader) { + public EmpiProviderR4(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader, IEmpiExpungeSvc theEmpiExpungeSvc) { super(theFhirContext, theResourceLoader); myEmpiMatchFinderSvc = theEmpiMatchFinderSvc; myPersonMergerSvc = thePersonMergerSvc; myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc; myEmpiLinkQuerySvc = theEmpiLinkQuerySvc; + myEmpiExpungeSvc = theEmpiExpungeSvc; } @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) @@ -117,6 +120,16 @@ public class EmpiProviderR4 extends BaseEmpiProvider { return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails)); } + @Operation(name = ProviderConstants.EMPI_CLEAR) + public Parameters clearEmpiLinks(@OperationParam(name=ProviderConstants.EMPI_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType) { + if (theTargetType == null) { + myEmpiExpungeSvc.expungeEmpiLinks(); + } else { + myEmpiExpungeSvc.expungeEmpiLinks(theTargetType.getValueNotNull()); + } + return new Parameters(); + } + @Operation(name = ProviderConstants.EMPI_QUERY_LINKS, idempotent = true) public Parameters queryLinks(@OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_PERSON_ID, min = 0, max = 1) StringType thePersonId, @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_TARGET_ID, min = 0, max = 1) StringType theTargetId, diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java index 2e58d1bc04c..89cfae0dc61 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java @@ -81,4 +81,7 @@ public class ProviderConstants { public static final String EMPI_DUPLICATE_PERSONS = "$empi-duplicate-persons"; public static final String EMPI_NOT_DUPLICATE = "$empi-not-duplicate"; + + public static final String EMPI_CLEAR = "$empi-clear"; + public static final String EMPI_CLEAR_TARGET_TYPE = "targetType"; }