Merge pull request #1984 from jamesagnew/batch-empi-job
This commit is contained in:
commit
750d693fc3
|
@ -1 +1,2 @@
|
|||
--- []
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 1984
|
||||
title: "Two new operations have been added for EMPI: `$empi-clear` and `$empi-submit`. `$empi-clear` will delete EMPI links,
|
||||
and related Person objects. `$empi-submit` will submit all matching resources for EMPI processing."
|
|
@ -20,3 +20,4 @@
|
|||
The method `FhirContext#getResourceNames()` has been renamed to `FhirContext#getResourceTypes()`. HAPI currently
|
||||
goes back and forth between the two, but is consolidating on `Types`.
|
||||
"
|
||||
|
||||
|
|
|
@ -447,3 +447,141 @@ This might result in a response such as the following:
|
|||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Clearing EMPI Links
|
||||
|
||||
The `$empi-clear` operation is used to batch-delete EMPI links and related persons from the database. This operation is meant to
|
||||
be used during the rules-tuning phase of the EMPI implementation so that you can quickly test your ruleset.
|
||||
It permits the user to reset the state of their EMPI system without manual deletion of all related links and Persons.
|
||||
|
||||
After the operation is complete, all targeted EMPI links are removed from the system, and their related Person resources are deleted and expunged
|
||||
from the server.
|
||||
|
||||
This operation takes a single optional Parameter.
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Cardinality</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>resourceType</td>
|
||||
<td>String</td>
|
||||
<td>0..1</td>
|
||||
<td>
|
||||
The target Resource type you would like to clear. Currently limited to Patient/Practitioner. If omitted, will operate over all links.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Example
|
||||
|
||||
Use an HTTP POST to the following URL to invoke this operation:
|
||||
|
||||
```url
|
||||
http://example.com/$empi-clear
|
||||
```
|
||||
|
||||
The following request body could be used:
|
||||
|
||||
```json
|
||||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [ {
|
||||
"name": "resourceType",
|
||||
"valueString": "Patient"
|
||||
} ]
|
||||
}
|
||||
```
|
||||
|
||||
This operation returns the number of EMPI links that were cleared. The following is a sample response:
|
||||
|
||||
```json
|
||||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [ {
|
||||
"name": "reset",
|
||||
"valueDecimal": 5
|
||||
} ]
|
||||
}
|
||||
```
|
||||
|
||||
## Batch-creating EMPI Links
|
||||
|
||||
Call the `$empi-submit` operation to submit patients and practitioners for EMPI processing. In the rules-tuning phase of your setup, you can use `$empi-submit` to apply EMPI rules across multiple Resources.
|
||||
An important thing to note is that this operation only submits the resources for processing. Actual EMPI processing is run asynchronously, and depending on the size
|
||||
of the affected bundle of resources, may take some time to complete.
|
||||
|
||||
After the operation is complete, all resources that matched the criteria will now have at least one EMPI link attached to them.
|
||||
|
||||
This operation takes a single optional criteria parameter unless it is called on a specific instance.
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Cardinality</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>criteria</td>
|
||||
<td>String</td>
|
||||
<td>0..1</td>
|
||||
<td>
|
||||
The search critiera used to filter resources.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Example
|
||||
|
||||
This operation can be executed at the Server level, Resource level, or Instance level.
|
||||
Use an HTTP POST to the following URL to invoke this operation with matching criteria:
|
||||
|
||||
```url
|
||||
http://example.com/$empi-submit
|
||||
http://example.com/Patient/$empi-submit
|
||||
http://example.com/Practitioner/$empi-submit
|
||||
```
|
||||
|
||||
The following request body could be used:
|
||||
|
||||
```json
|
||||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [ {
|
||||
"criteria": "",
|
||||
"valueString": "birthDate=2020-07-28"
|
||||
} ]
|
||||
}
|
||||
```
|
||||
This operation returns the number of resources that were submitted for EMPI processing. The following is a sample response:
|
||||
|
||||
```json
|
||||
{
|
||||
"resourceType": "Parameters",
|
||||
"parameter": [ {
|
||||
"name": "submitted",
|
||||
"valueDecimal": 5
|
||||
} ]
|
||||
}
|
||||
```
|
||||
|
||||
This operation can also be done at the Instance level. When this is the case, the operations accepts no parameters.
|
||||
The following are examples of Instance level POSTs, which require no parameters.
|
||||
|
||||
```url
|
||||
http://example.com/Patient/123/$empi-submit
|
||||
http://example.com/Practitioner/456/$empi-submit
|
||||
```
|
||||
|
||||
|
|
|
@ -127,6 +127,10 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||
return myList.size();
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
this.removeIf(x -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myList.toString();
|
||||
|
|
|
@ -78,13 +78,11 @@ public class BulkExportJobConfig {
|
|||
return new CreateBulkExportEntityTasklet();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public JobParametersValidator bulkJobParameterValidator() {
|
||||
return new BulkExportJobParameterValidator();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Step bulkExportGenerateResourceFilesStep() {
|
||||
return myStepBuilderFactory.get("bulkExportGenerateResourceFilesStep")
|
||||
|
@ -95,8 +93,6 @@ public class BulkExportJobConfig {
|
|||
.build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
@JobScope
|
||||
public BulkExportJobCloser bulkExportJobCloser() {
|
||||
|
|
|
@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
|||
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
|
@ -136,8 +135,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Autowired
|
||||
protected PlatformTransactionManager myPlatformTransactionManager;
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc mySearchDao;
|
||||
@Autowired
|
||||
protected DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
|
@ -148,16 +145,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
private SearchBuilderFactory mySearchBuilderFactory;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||
@Autowired
|
||||
private HapiTransactionService myTransactionService;
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc mySearchDao;
|
||||
|
||||
private IInstanceValidatorModule myInstanceValidator;
|
||||
private String myResourceName;
|
||||
private Class<T> myResourceType;
|
||||
@Autowired
|
||||
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||
@Autowired
|
||||
private PartitionSettings myPartitionSettings;
|
||||
@Autowired
|
||||
private HapiTransactionService myTransactionService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
|
@ -707,6 +704,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return myResourceName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<T> getResourceType() {
|
||||
return myResourceType;
|
||||
|
|
|
@ -23,12 +23,15 @@ package ca.uhn.fhir.jpa.dao;
|
|||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface IResultIterator extends Iterator<ResourcePersistentId>, Closeable {
|
||||
|
||||
int getSkippedCount();
|
||||
|
||||
int getNonSkippedCount();
|
||||
int getNonSkippedCount();
|
||||
|
||||
Collection<ResourcePersistentId> getNextResultBatch(long theBatchSize);
|
||||
|
||||
}
|
||||
|
|
|
@ -1306,12 +1306,22 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return myNonSkipCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePersistentId> getNextResultBatch(long theBatchSize) {
|
||||
Collection<ResourcePersistentId> batch = new ArrayList<>();
|
||||
while (this.hasNext() && batch.size() < theBatchSize) {
|
||||
batch.add(this.next());
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (myResultsIterator != null) {
|
||||
myResultsIterator.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CountQueryIterator implements Iterator<Long> {
|
||||
|
|
|
@ -33,9 +33,9 @@ public interface IResourceExpungeService {
|
|||
|
||||
void expungeHistoricalVersions(RequestDetails theRequestDetails, List<Long> thePartition, AtomicInteger theRemainingCount);
|
||||
|
||||
void expungeCurrentVersionOfResources(RequestDetails theRequestDetails, List<Long> thePartition, AtomicInteger theRemainingCount);
|
||||
void expungeCurrentVersionOfResources(RequestDetails theRequestDetails, List<Long> theResourceIds, AtomicInteger theRemainingCount);
|
||||
|
||||
void expungeHistoricalVersionsOfIds(RequestDetails theRequestDetails, List<Long> thePartition, AtomicInteger theRemainingCount);
|
||||
void expungeHistoricalVersionsOfIds(RequestDetails theRequestDetails, List<Long> theResourceIds, AtomicInteger theRemainingCount);
|
||||
|
||||
void deleteAllSearchParams(Long theResourceId);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ import java.util.List;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Service
|
||||
class ResourceExpungeService implements IResourceExpungeService {
|
||||
public class ResourceExpungeService implements IResourceExpungeService {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ResourceExpungeService.class);
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -483,7 +478,7 @@ public class IdHelperService {
|
|||
}
|
||||
|
||||
public Map<Long, IIdType> getPidToIdMap(Collection<IIdType> 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) {
|
||||
|
|
|
@ -51,6 +51,8 @@ public class EmpiLink {
|
|||
public static final int VERSION_LENGTH = 16;
|
||||
private static final int MATCH_RESULT_LENGTH = 16;
|
||||
private static final int LINK_SOURCE_LENGTH = 16;
|
||||
public static final int TARGET_TYPE_LENGTH = 40;
|
||||
|
||||
|
||||
@SequenceGenerator(name = "SEQ_EMPI_LINK_ID", sequenceName = "SEQ_EMPI_LINK_ID")
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_EMPI_LINK_ID")
|
||||
|
@ -111,6 +113,9 @@ public class EmpiLink {
|
|||
myVersion = theVersion;
|
||||
}
|
||||
|
||||
@Column(name = "TARGET_TYPE", nullable = true, length = TARGET_TYPE_LENGTH)
|
||||
private String myEmpiTargetType;
|
||||
|
||||
public Long getId() {
|
||||
return myId;
|
||||
}
|
||||
|
@ -276,6 +281,7 @@ public class EmpiLink {
|
|||
return new ToStringBuilder(this)
|
||||
.append("myPersonPid", myPersonPid)
|
||||
.append("myTargetPid", myTargetPid)
|
||||
.append("myEmpiTargetType", myEmpiTargetType)
|
||||
.append("myMatchResult", myMatchResult)
|
||||
.append("myLinkSource", myLinkSource)
|
||||
.append("myEidMatch", myEidMatch)
|
||||
|
@ -283,4 +289,12 @@ public class EmpiLink {
|
|||
.append("myScore", myScore)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getEmpiTargetType() {
|
||||
return myEmpiTargetType;
|
||||
}
|
||||
|
||||
public void setEmpiTargetType(String theEmpiTargetType) {
|
||||
myEmpiTargetType = theEmpiTargetType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
|
@ -77,6 +76,7 @@ public abstract class BaseJpaResourceProvider<T extends IBaseResource> extends B
|
|||
return createExpungeResponse(outcome);
|
||||
}
|
||||
|
||||
|
||||
public IFhirResourceDao<T> getDao() {
|
||||
return myDao;
|
||||
}
|
||||
|
|
|
@ -112,6 +112,8 @@ public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResour
|
|||
return super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "return", type = Meta.class)
|
||||
})
|
||||
|
|
|
@ -221,6 +221,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||
return template.execute(theStatus -> toResourceList(sb, pidsSubList));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns false if the entity can't be found
|
||||
*/
|
||||
|
|
|
@ -13,7 +13,6 @@ import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
|||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||
|
@ -24,12 +23,11 @@ import ca.uhn.fhir.model.dstu2.resource.Patient;
|
|||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -668,6 +666,15 @@ public class SearchCoordinatorSvcImplTest {
|
|||
return myCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePersistentId> getNextResultBatch(long theBatchSize) {
|
||||
Collection<ResourcePersistentId> batch = new ArrayList<>();
|
||||
while (this.hasNext() && batch.size() < theBatchSize) {
|
||||
batch.add(this.next());
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing
|
||||
|
@ -704,6 +711,15 @@ public class SearchCoordinatorSvcImplTest {
|
|||
return myCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePersistentId> getNextResultBatch(long theBatchSize) {
|
||||
Collection<ResourcePersistentId> batch = new ArrayList<>();
|
||||
while (this.hasNext() && batch.size() < theBatchSize) {
|
||||
batch.add(this.next());
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing
|
||||
|
@ -775,6 +791,15 @@ public class SearchCoordinatorSvcImplTest {
|
|||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourcePersistentId> getNextResultBatch(long theBatchSize) {
|
||||
Collection<ResourcePersistentId> batch = new ArrayList<>();
|
||||
while (this.hasNext() && batch.size() < theBatchSize) {
|
||||
batch.add(this.next());
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing
|
||||
|
|
|
@ -79,6 +79,7 @@ public class EmpiMessageHandler implements MessageHandler {
|
|||
handleCreatePatientOrPractitioner(theMsg, empiContext);
|
||||
break;
|
||||
case UPDATE:
|
||||
case MANUALLY_TRIGGERED:
|
||||
handleUpdatePatientOrPractitioner(theMsg, empiContext);
|
||||
break;
|
||||
case DELETE:
|
||||
|
@ -106,6 +107,9 @@ public class EmpiMessageHandler implements MessageHandler {
|
|||
case UPDATE:
|
||||
empiOperation = EmpiTransactionContext.OperationType.UPDATE;
|
||||
break;
|
||||
case MANUALLY_TRIGGERED:
|
||||
empiOperation = EmpiTransactionContext.OperationType.BATCH;
|
||||
break;
|
||||
case DELETE:
|
||||
default:
|
||||
ourLog.trace("Not creating an EmpiTransactionContext for {}", theMsg.getOperationType());
|
||||
|
|
|
@ -26,6 +26,7 @@ import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
|||
import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||
import ca.uhn.fhir.empi.log.Logs;
|
||||
import ca.uhn.fhir.empi.provider.EmpiProviderLoader;
|
||||
|
@ -46,7 +47,9 @@ import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl;
|
|||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonDeletingSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiResetSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchCriteriaBuilderSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchSvc;
|
||||
|
@ -159,6 +162,11 @@ public class EmpiConsumerConfig {
|
|||
return new EmpiLinkQuerySvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
IEmpiResetSvc empiResetSvc(EmpiLinkDaoSvc theEmpiLinkDaoSvc, EmpiPersonDeletingSvc theEmpiPersonDeletingSvcImpl ) {
|
||||
return new EmpiResetSvcImpl(theEmpiLinkDaoSvc, theEmpiPersonDeletingSvcImpl);
|
||||
}
|
||||
|
||||
@Bean
|
||||
EmpiCandidateSearchSvc empiCandidateSearchSvc() {
|
||||
return new EmpiCandidateSearchSvc();
|
||||
|
@ -190,7 +198,7 @@ public class EmpiConsumerConfig {
|
|||
}
|
||||
|
||||
@Bean
|
||||
IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() {
|
||||
IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() {
|
||||
return new EmpiLinkUpdaterSvcImpl();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,12 +21,19 @@ package ca.uhn.fhir.jpa.empi.config;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.empi.api.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiChannelSubmitterSvc;
|
||||
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
||||
import ca.uhn.fhir.jpa.dao.empi.EmpiLinkDeleteSvc;
|
||||
import ca.uhn.fhir.jpa.empi.interceptor.EmpiSubmitterInterceptorLoader;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiBatchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiChannelSubmitterSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonDeletingSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
@Configuration
|
||||
public class EmpiSubmitterConfig {
|
||||
|
@ -50,4 +57,20 @@ public class EmpiSubmitterConfig {
|
|||
EmpiLinkDeleteSvc empiLinkDeleteSvc() {
|
||||
return new EmpiLinkDeleteSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
EmpiPersonDeletingSvc empiPersonDeletingSvc() {
|
||||
return new EmpiPersonDeletingSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
IEmpiChannelSubmitterSvc empiChannelSubmitterSvc(FhirContext theFhirContext, IChannelFactory theChannelFactory) {
|
||||
return new EmpiChannelSubmitterSvcImpl(theFhirContext, theChannelFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
IEmpiBatchSvc empiBatchService() {
|
||||
return new EmpiBatchSvcImpl();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.empi.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||
|
@ -29,7 +30,6 @@ import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
|
|||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
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 +42,7 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EmpiLinkDaoSvc {
|
||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||
|
@ -52,6 +53,8 @@ public class EmpiLinkDaoSvc {
|
|||
private EmpiLinkFactory myEmpiLinkFactory;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
@Transactional
|
||||
public EmpiLink createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theTarget, EmpiMatchOutcome theMatchOutcome, EmpiLinkSourceEnum theLinkSource, @Nullable EmpiTransactionContext theEmpiTransactionContext) {
|
||||
|
@ -64,6 +67,7 @@ public class EmpiLinkDaoSvc {
|
|||
// Preserve these flags for link updates
|
||||
empiLink.setEidMatch(theMatchOutcome.isEidMatch() | empiLink.isEidMatch());
|
||||
empiLink.setNewPerson(theMatchOutcome.isNewPerson() | empiLink.isNewPerson());
|
||||
empiLink.setEmpiTargetType(myFhirContext.getResourceType(theTarget));
|
||||
if (empiLink.getScore() != null) {
|
||||
empiLink.setScore(Math.max(theMatchOutcome.score, empiLink.getScore()));
|
||||
} else {
|
||||
|
@ -77,7 +81,6 @@ public class EmpiLinkDaoSvc {
|
|||
return empiLink;
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
public EmpiLink getOrCreateEmpiLinkByPersonPidAndTargetPid(Long thePersonPid, Long theResourcePid) {
|
||||
Optional<EmpiLink> oExisting = getLinkByPersonPidAndTargetPid(thePersonPid, theResourcePid);
|
||||
|
@ -103,6 +106,14 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findOne(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a Target Pid, and a match result, return all links that match these criteria.
|
||||
*
|
||||
* @param theTargetPid the target of the relationship.
|
||||
* @param theMatchResult the Match Result of the relationship
|
||||
*
|
||||
* @return a list of {@link EmpiLink} entities matching these criteria.
|
||||
*/
|
||||
public List<EmpiLink> getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setTargetPid(theTargetPid);
|
||||
|
@ -111,6 +122,13 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findAll(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a target Pid, return its Matched EmpiLink. There can only ever be at most one of these, but its possible
|
||||
* the target has no matches, and may return an empty optional.
|
||||
*
|
||||
* @param theTargetPid The Pid of the target you wish to find the matching link for.
|
||||
* @return the {@link EmpiLink} that contains the Match information for the target.
|
||||
*/
|
||||
public Optional<EmpiLink> getMatchedLinkForTargetPid(Long theTargetPid) {
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setTargetPid(theTargetPid);
|
||||
|
@ -119,6 +137,13 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findOne(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IBaseResource, return its Matched EmpiLink. There can only ever be at most one of these, but its possible
|
||||
* the target has no matches, and may return an empty optional.
|
||||
*
|
||||
* @param theTarget The IBaseResource representing the target you wish to find the matching link for.
|
||||
* @return the {@link EmpiLink} that contains the Match information for the target.
|
||||
*/
|
||||
public Optional<EmpiLink> getMatchedLinkForTarget(IBaseResource theTarget) {
|
||||
Long pid = myIdHelperService.getPidOrNull(theTarget);
|
||||
if (pid == null) {
|
||||
|
@ -132,6 +157,15 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findOne(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a person a target and a match result, return the matching EmpiLink, if it exists.
|
||||
*
|
||||
* @param thePersonPid The Pid of the Person in the relationship
|
||||
* @param theTargetPid The Pid of the target in the relationship
|
||||
* @param theMatchResult The MatchResult you are looking for.
|
||||
*
|
||||
* @return an Optional {@link EmpiLink} containing the matched link if it exists.
|
||||
*/
|
||||
public Optional<EmpiLink> getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setPersonPid(thePersonPid);
|
||||
|
@ -163,12 +197,25 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findOne(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given EmpiLink. Note that this does not clear out the Person, or the Person's related links.
|
||||
* It is a simple entity delete.
|
||||
*
|
||||
* @param theEmpiLink the EmpiLink to delete.
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void deleteLink(EmpiLink theEmpiLink) {
|
||||
myEmpiLinkDao.delete(theEmpiLink);
|
||||
}
|
||||
|
||||
public List<EmpiLink> findEmpiLinksByPersonId(IBaseResource thePersonResource) {
|
||||
/**
|
||||
* Given a Person, return all links in which they are the source Person of the {@link EmpiLink}
|
||||
*
|
||||
* @param thePersonResource The {@link IBaseResource} Person who's links you would like to retrieve.
|
||||
*
|
||||
* @return A list of all {@link EmpiLink} entities in which thePersonResource is the source Person.
|
||||
*/
|
||||
public List<EmpiLink> findEmpiLinksByPerson(IBaseResource thePersonResource) {
|
||||
Long pid = myIdHelperService.getPidOrNull(thePersonResource);
|
||||
if (pid == null) {
|
||||
return Collections.emptyList();
|
||||
|
@ -178,6 +225,46 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findAll(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all {@link EmpiLink} entities, and return all resource PIDs from the source of the relationship.
|
||||
*
|
||||
* @return A list of Long representing the related Person Pids.
|
||||
*/
|
||||
@Transactional
|
||||
public List<Long> deleteAllEmpiLinksAndReturnPersonPids() {
|
||||
List<EmpiLink> all = myEmpiLinkDao.findAll();
|
||||
return deleteEmpiLinksAndReturnPersonPids(all);
|
||||
}
|
||||
|
||||
private List<Long> deleteEmpiLinksAndReturnPersonPids(List<EmpiLink> theLinks) {
|
||||
List<Long> collect = theLinks.stream().map(EmpiLink::getPersonPid).distinct().collect(Collectors.toList());
|
||||
myEmpiLinkDao.deleteAll(theLinks);
|
||||
return collect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a valid {@link String}, delete all {@link EmpiLink} entities for that type, and get the Pids
|
||||
* for the Person resources which were the sources of the links.
|
||||
*
|
||||
* @param theTargetType the type of relationship you would like to delete.
|
||||
*
|
||||
* @return A list of longs representing the Pids of the Person resources used as the sources of the relationships that were deleted.
|
||||
*/
|
||||
public List<Long> deleteAllEmpiLinksOfTypeAndReturnPersonPids(String theTargetType) {
|
||||
EmpiLink link = new EmpiLink();
|
||||
link.setEmpiTargetType(theTargetType);
|
||||
Example<EmpiLink> exampleLink = Example.of(link);
|
||||
List<EmpiLink> allOfType = myEmpiLinkDao.findAll(exampleLink);
|
||||
return deleteEmpiLinksAndReturnPersonPids(allOfType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist an EmpiLink to the database.
|
||||
*
|
||||
* @param theEmpiLink the link to save.
|
||||
*
|
||||
* @return the persisted {@link EmpiLink} entity.
|
||||
*/
|
||||
public EmpiLink save(EmpiLink theEmpiLink) {
|
||||
if (theEmpiLink.getCreated() == null) {
|
||||
theEmpiLink.setCreated(new Date());
|
||||
|
@ -186,11 +273,27 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.save(theEmpiLink);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given an example {@link EmpiLink}, return all links from the database which match the example.
|
||||
*
|
||||
* @param theExampleLink The EmpiLink containing the data we would like to search for.
|
||||
*
|
||||
* @return a list of {@link EmpiLink} entities which match the example.
|
||||
*/
|
||||
public List<EmpiLink> findEmpiLinkByExample(Example<EmpiLink> theExampleLink) {
|
||||
return myEmpiLinkDao.findAll(theExampleLink);
|
||||
}
|
||||
|
||||
public List<EmpiLink> findEmpiLinksByTarget(Patient theTargetResource) {
|
||||
/**
|
||||
* Given a target {@link IBaseResource}, return all {@link EmpiLink} entities in which this target is the target
|
||||
* of the relationship. This will show you all links for a given Patient/Practitioner.
|
||||
*
|
||||
* @param theTargetResource the target resource to find links for.
|
||||
*
|
||||
* @return all links for the target.
|
||||
*/
|
||||
public List<EmpiLink> findEmpiLinksByTarget(IBaseResource theTargetResource) {
|
||||
Long pid = myIdHelperService.getPidOrNull(theTargetResource);
|
||||
if (pid == null) {
|
||||
return Collections.emptyList();
|
||||
|
@ -200,7 +303,13 @@ public class EmpiLinkDaoSvc {
|
|||
return myEmpiLinkDao.findAll(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory delegation method, whenever you need a new EmpiLink, use this factory method.
|
||||
* //TODO Should we make the constructor private for EmpiLink? or work out some way to ensure they can only be instantiated via factory.
|
||||
* @return A new {@link EmpiLink}.
|
||||
*/
|
||||
public EmpiLink newEmpiLink() {
|
||||
return myEmpiLinkFactory.newEmpiLink();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ public class EmpiLinkFactory {
|
|||
myEmpiSettings = theEmpiSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new EmpiLink, populating it with the version of the ruleset used to create it.
|
||||
*
|
||||
* @return the new {@link EmpiLink}
|
||||
*/
|
||||
public EmpiLink newEmpiLink() {
|
||||
return new EmpiLink(myEmpiSettings.getRuleVersion());
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
|||
@Autowired
|
||||
private PersonHelper myPersonHelper;
|
||||
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
|
||||
public void blockManualPersonManipulationOnCreate(IBaseResource theBaseResource, RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails) {
|
||||
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
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.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiChannelSubmitterSvc;
|
||||
import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
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.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EmpiBatchSvcImpl implements IEmpiBatchSvc {
|
||||
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@Autowired
|
||||
private EmpiSearchParamSvc myEmpiSearchParamSvc;
|
||||
|
||||
@Autowired
|
||||
private IEmpiChannelSubmitterSvc myEmpiChannelSubmitterSvc;
|
||||
|
||||
private static final int BUFFER_SIZE = 100;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long runEmpiOnAllTargetTypes(String theCriteria) {
|
||||
long submittedCount = 0;
|
||||
submittedCount += runEmpiOnPatientType(theCriteria);
|
||||
submittedCount += runEmpiOnPractitionerType(theCriteria);
|
||||
return submittedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long runEmpiOnTargetType(String theTargetType, String theCriteria) {
|
||||
resolveTargetTypeOrThrowException(theTargetType);
|
||||
SearchParameterMap spMap = myEmpiSearchParamSvc.getSearchParameterMapFromCriteria(theTargetType, theCriteria);
|
||||
spMap.setLoadSynchronousUpTo(BUFFER_SIZE);
|
||||
ISearchBuilder searchBuilder = myEmpiSearchParamSvc.generateSearchBuilderForType(theTargetType);
|
||||
return submitAllMatchingResourcesToEmpiChannel(spMap, searchBuilder);
|
||||
}
|
||||
|
||||
private long submitAllMatchingResourcesToEmpiChannel(SearchParameterMap theSpMap, ISearchBuilder theSearchBuilder) {
|
||||
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(null, UUID.randomUUID().toString());
|
||||
long total = 0;
|
||||
try (IResultIterator query = theSearchBuilder.createQuery(theSpMap, searchRuntimeDetails, null, RequestPartitionId.defaultPartition())) {
|
||||
Collection<ResourcePersistentId> pidBatch;
|
||||
do {
|
||||
pidBatch = query.getNextResultBatch(BUFFER_SIZE);
|
||||
total += loadPidsAndSubmitToEmpiChannel(theSearchBuilder, pidBatch);
|
||||
} while (query.hasNext());
|
||||
} catch (IOException theE) {
|
||||
throw new InternalErrorException("Failure while attempting to query resources for " + ProviderConstants.OPERATION_EMPI_BATCH_RUN, theE);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a collection of ResourcePersistentId objects, and a search builder, load the IBaseResources and submit them to
|
||||
* the EMPI channel for processing.
|
||||
*
|
||||
* @param theSearchBuilder the related DAO search builder.
|
||||
* @param thePidsToSubmit The collection of PIDs whos resources you want to submit for EMPI processing.
|
||||
*
|
||||
* @return The total count of submitted resources.
|
||||
*/
|
||||
private long loadPidsAndSubmitToEmpiChannel(ISearchBuilder theSearchBuilder, Collection<ResourcePersistentId> thePidsToSubmit) {
|
||||
List<IBaseResource> resourcesToSubmit = new ArrayList<>();
|
||||
theSearchBuilder.loadResourcesByPid(thePidsToSubmit, Collections.emptyList(), resourcesToSubmit, false, null);
|
||||
resourcesToSubmit
|
||||
.forEach(resource -> myEmpiChannelSubmitterSvc.submitResourceToEmpiChannel(resource));
|
||||
return resourcesToSubmit.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long runEmpiOnPractitionerType(String theCriteria) {
|
||||
return runEmpiOnTargetType("Practitioner", theCriteria);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long runEmpiOnPatientType(String theCriteria) {
|
||||
return runEmpiOnTargetType("Patient", theCriteria);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long runEmpiOnTarget(IIdType theId) {
|
||||
resolveTargetTypeOrThrowException(theId.getResourceType());
|
||||
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType());
|
||||
IBaseResource read = resourceDao.read(theId);
|
||||
myEmpiChannelSubmitterSvc.submitResourceToEmpiChannel(read);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void resolveTargetTypeOrThrowException(String theResourceType) {
|
||||
if (!EmpiUtil.supportedTargetType(theResourceType)) {
|
||||
throw new InvalidRequestException(ProviderConstants.OPERATION_EMPI_BATCH_RUN + " does not support resource type: " + theResourceType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.context.FhirContext;
|
||||
import ca.uhn.fhir.empi.api.IEmpiChannelSubmitterSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import static ca.uhn.fhir.empi.api.IEmpiSettings.EMPI_CHANNEL_NAME;
|
||||
|
||||
/**
|
||||
* This class is responsible for manual submissions of {@link IAnyResource} resources onto the Empi Channel.
|
||||
*/
|
||||
public class EmpiChannelSubmitterSvcImpl implements IEmpiChannelSubmitterSvc {
|
||||
private MessageChannel myEmpiChannelProducer;
|
||||
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
private IChannelFactory myChannelFactory;
|
||||
|
||||
@Override
|
||||
public void submitResourceToEmpiChannel(IBaseResource theResource) {
|
||||
ResourceModifiedJsonMessage resourceModifiedJsonMessage = new ResourceModifiedJsonMessage();
|
||||
ResourceModifiedMessage resourceModifiedMessage = new ResourceModifiedMessage(myFhirContext, theResource, ResourceModifiedMessage.OperationTypeEnum.MANUALLY_TRIGGERED);
|
||||
resourceModifiedMessage.setOperationType(ResourceModifiedMessage.OperationTypeEnum.MANUALLY_TRIGGERED);
|
||||
resourceModifiedJsonMessage.setPayload(resourceModifiedMessage);
|
||||
getEmpiChannelProducer().send(resourceModifiedJsonMessage);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public EmpiChannelSubmitterSvcImpl(FhirContext theFhirContext, IChannelFactory theIChannelFactory) {
|
||||
myFhirContext = theFhirContext;
|
||||
myChannelFactory = theIChannelFactory;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
ChannelProducerSettings channelSettings = new ChannelProducerSettings();
|
||||
myEmpiChannelProducer= myChannelFactory.getOrCreateProducer(EMPI_CHANNEL_NAME, ResourceModifiedJsonMessage.class, channelSettings);
|
||||
}
|
||||
|
||||
private MessageChannel getEmpiChannelProducer() {
|
||||
if (myEmpiChannelProducer == null) {
|
||||
init();
|
||||
}
|
||||
return myEmpiChannelProducer;
|
||||
}
|
||||
}
|
|
@ -107,7 +107,7 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
|||
public void syncEmpiLinksToPersonLinks(IAnyResource thePersonResource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
int origLinkCount = myPersonHelper.getLinkCount(thePersonResource);
|
||||
|
||||
List<EmpiLink> empiLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonResource);
|
||||
List<EmpiLink> empiLinks = myEmpiLinkDaoSvc.findEmpiLinksByPerson(thePersonResource);
|
||||
|
||||
List<IBaseBackboneElement> newLinks = empiLinks.stream()
|
||||
.filter(link -> link.isMatch() || link.isPossibleMatch())
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
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.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.api.model.ExpungeOptions;
|
||||
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
@Service
|
||||
public class EmpiPersonDeletingSvc {
|
||||
private static final Logger ourLog = getLogger(EmpiPersonDeletingSvc.class);
|
||||
/**
|
||||
* This is here for the case of possible infinite loops. Technically batch conflict deletion should handle this, but this is an escape hatch.
|
||||
*/
|
||||
private static final int MAXIMUM_DELETE_ATTEMPTS = 100000;
|
||||
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private ExpungeService myExpungeService;
|
||||
|
||||
/**
|
||||
* Function which will delete all resources by their PIDs, and also delete any resources that were undeletable due to
|
||||
* VersionConflictException
|
||||
*
|
||||
* @param theLongs
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteResourcesAndHandleConflicts(List<Long> theLongs) {
|
||||
DeleteConflictList
|
||||
deleteConflictList = new DeleteConflictList();
|
||||
theLongs.stream().forEach(pid -> deleteCascade(pid, deleteConflictList));
|
||||
|
||||
IFhirResourceDao personDao = myDaoRegistry.getResourceDao("Person");
|
||||
int batchCount = 0;
|
||||
while (!deleteConflictList.isEmpty()) {
|
||||
deleteConflictBatch(deleteConflictList, personDao);
|
||||
batchCount += 1;
|
||||
if (batchCount > MAXIMUM_DELETE_ATTEMPTS) {
|
||||
throw new IllegalStateException("Person deletion seems to have entered an infinite loop. Aborting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the expunge service to expunge all historical and current versions of the resources associated to the PIDs.
|
||||
*/
|
||||
public void expungeHistoricalAndCurrentVersionsOfIds(List<Long> theLongs) {
|
||||
ExpungeOptions options = new ExpungeOptions();
|
||||
options.setExpungeDeletedResources(true);
|
||||
options.setExpungeOldVersions(true);
|
||||
theLongs
|
||||
.forEach(personId -> myExpungeService.expunge("Person", personId, null, options, null));
|
||||
}
|
||||
|
||||
private void deleteCascade(Long pid, DeleteConflictList theDeleteConflictList) {
|
||||
ourLog.debug("About to cascade delete: {}", pid);
|
||||
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao("Person");
|
||||
resourceDao.delete(new IdType("Person/" + pid), theDeleteConflictList, null, null);
|
||||
}
|
||||
|
||||
private void deleteConflictBatch(DeleteConflictList theDcl, IFhirResourceDao<IBaseResource> theDao) {
|
||||
DeleteConflictList newBatch = new DeleteConflictList();
|
||||
for (DeleteConflict next : theDcl) {
|
||||
IdDt nextSource = next.getSourceId();
|
||||
ourLog.info("Have delete conflict {} - Cascading delete", nextSource);
|
||||
theDao.delete(nextSource.toVersionless(), newBatch, null, null);
|
||||
}
|
||||
theDcl.removeAll();
|
||||
theDcl.addAll(newBatch);
|
||||
}
|
||||
}
|
|
@ -90,8 +90,8 @@ public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc {
|
|||
}
|
||||
|
||||
private void mergeLinks(IAnyResource theFromPerson, IAnyResource theToPerson, Long theToPersonPid, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
List<EmpiLink> incomingLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(theFromPerson);
|
||||
List<EmpiLink> origLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(theToPerson);
|
||||
List<EmpiLink> incomingLinks = myEmpiLinkDaoSvc.findEmpiLinksByPerson(theFromPerson);
|
||||
List<EmpiLink> origLinks = myEmpiLinkDaoSvc.findEmpiLinksByPerson(theToPerson);
|
||||
|
||||
// For each incomingLink, either ignore it, move it, or replace the original one
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
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.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This class is responsible for clearing out existing EMPI links, as well as deleting all persons related to those EMPI Links.
|
||||
*
|
||||
*/
|
||||
public class EmpiResetSvcImpl implements IEmpiResetSvc {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(EmpiResetSvcImpl.class);
|
||||
|
||||
final EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||
final EmpiPersonDeletingSvc myEmpiPersonDeletingSvcImpl;
|
||||
|
||||
@Autowired
|
||||
public EmpiResetSvcImpl(EmpiLinkDaoSvc theEmpiLinkDaoSvc, EmpiPersonDeletingSvc theEmpiPersonDeletingSvcImpl) {
|
||||
myEmpiLinkDaoSvc = theEmpiLinkDaoSvc;
|
||||
myEmpiPersonDeletingSvcImpl = theEmpiPersonDeletingSvcImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long expungeAllEmpiLinksOfTargetType(String theResourceType) {
|
||||
throwExceptionIfInvalidTargetType(theResourceType);
|
||||
List<Long> longs = myEmpiLinkDaoSvc.deleteAllEmpiLinksOfTypeAndReturnPersonPids(theResourceType);
|
||||
myEmpiPersonDeletingSvcImpl.deleteResourcesAndHandleConflicts(longs);
|
||||
myEmpiPersonDeletingSvcImpl.expungeHistoricalAndCurrentVersionsOfIds(longs);
|
||||
return longs.size();
|
||||
}
|
||||
|
||||
private void throwExceptionIfInvalidTargetType(String theResourceType) {
|
||||
if (!EmpiUtil.supportedTargetType(theResourceType)) {
|
||||
throw new InvalidRequestException(ProviderConstants.EMPI_CLEAR + " does not support resource type: " + theResourceType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeAllEmpiLinks() {
|
||||
List<Long> personPids = myEmpiLinkDaoSvc.deleteAllEmpiLinksAndReturnPersonPids();
|
||||
myEmpiPersonDeletingSvcImpl.deleteResourcesAndHandleConflicts(personPids);
|
||||
myEmpiPersonDeletingSvcImpl.expungeHistoricalAndCurrentVersionsOfIds(personPids);
|
||||
return personPids.size();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +23,16 @@ package ca.uhn.fhir.jpa.empi.svc;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -44,6 +49,10 @@ public class EmpiSearchParamSvc implements ISearchParamRetriever {
|
|||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
private SearchParamExtractorService mySearchParamExtractorService;
|
||||
@Autowired
|
||||
private SearchBuilderFactory mySearchBuilderFactory;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
public SearchParameterMap mapFromCriteria(String theResourceType, String theResourceCriteria) {
|
||||
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
|
||||
|
@ -60,4 +69,28 @@ public class EmpiSearchParamSvc implements ISearchParamRetriever {
|
|||
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
|
||||
return mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a target type, and a criteria string of the shape name=x&birthDate=y, generate a {@link SearchParameterMap}
|
||||
* that represents this query.
|
||||
*
|
||||
* @param theTargetType the resource type to execute the search on
|
||||
* @param theCriteria the string search criteria.
|
||||
*
|
||||
* @return the generated SearchParameterMap, or an empty one if there is no criteria.
|
||||
*/
|
||||
public SearchParameterMap getSearchParameterMapFromCriteria(String theTargetType, String theCriteria) {
|
||||
SearchParameterMap spMap;
|
||||
if (StringUtils.isBlank(theCriteria)) {
|
||||
spMap = new SearchParameterMap();
|
||||
} else {
|
||||
spMap = mapFromCriteria(theTargetType, theCriteria);
|
||||
}
|
||||
return spMap;
|
||||
}
|
||||
|
||||
public ISearchBuilder generateSearchBuilderForType(String theTargetType) {
|
||||
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theTargetType);
|
||||
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theTargetType, resourceDao.getResourceType());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
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.empi.api.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
||||
|
@ -74,6 +75,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;
|
||||
|
@ -101,6 +104,8 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
|||
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
|
||||
@Autowired
|
||||
SearchParamRegistryImpl mySearchParamRegistry;
|
||||
@Autowired
|
||||
private IEmpiBatchSvc myEmpiBatchService;
|
||||
|
||||
protected ServletRequestDetails myRequestDetails = new ServletRequestDetails(null);
|
||||
|
||||
|
@ -157,6 +162,14 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
|||
patient.setId(outcome.getId());
|
||||
return patient;
|
||||
}
|
||||
@Nonnull
|
||||
protected Practitioner createPractitioner(Practitioner thePractitioner) {
|
||||
//Note that since our empi-rules block on active=true, all patients must be active.
|
||||
thePractitioner.setActive(true);
|
||||
DaoMethodOutcome daoMethodOutcome = myPractitionerDao.create(thePractitioner);
|
||||
thePractitioner.setId(daoMethodOutcome.getId());
|
||||
return thePractitioner;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected Patient buildPatientWithNameAndId(String theGivenName, String theId) {
|
||||
|
@ -235,6 +248,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);
|
||||
|
|
|
@ -26,7 +26,6 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test {
|
|||
protected StringType myPersonId;
|
||||
protected StringType myVersionlessPersonId;
|
||||
|
||||
|
||||
@Override
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
@ -51,8 +50,10 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test {
|
|||
return myEmpiLinkDaoSvc.findEmpiLinkByTarget(myPatient).get();
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
protected List<EmpiLink> getPatientLinks() {
|
||||
return myEmpiLinkDaoSvc.findEmpiLinksByTarget(myPatient);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.empi.provider;
|
||||
|
||||
import ca.uhn.fhir.empi.api.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||
|
@ -33,6 +35,10 @@ public abstract class BaseProviderR4Test extends BaseEmpiR4Test {
|
|||
private IResourceLoader myResourceLoader;
|
||||
@Autowired
|
||||
private IEmpiSettings myEmpiSettings;
|
||||
@Autowired
|
||||
private IEmpiResetSvc myEmpiExpungeSvc;
|
||||
@Autowired
|
||||
private IEmpiBatchSvc myEmpiBatchSvc;
|
||||
|
||||
private String defaultScript;
|
||||
|
||||
|
@ -46,7 +52,7 @@ public abstract class BaseProviderR4Test extends BaseEmpiR4Test {
|
|||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myEmpiProviderR4 = new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader);
|
||||
myEmpiProviderR4 = new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader, myEmpiExpungeSvc, myEmpiBatchSvc);
|
||||
defaultScript = ((EmpiSettings)myEmpiSettings).getScriptText();
|
||||
}
|
||||
@AfterEach
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package ca.uhn.fhir.jpa.empi.provider;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.test.concurrency.PointcutLatch;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Person;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class EmpiProviderBatchR4Test extends BaseLinkR4Test {
|
||||
|
||||
protected Practitioner myPractitioner;
|
||||
protected StringType myPractitionerId;
|
||||
protected Person myPractitionerPerson;
|
||||
protected StringType myPractitionerPersonId;
|
||||
|
||||
@Autowired
|
||||
IInterceptorService myInterceptorService;
|
||||
PointcutLatch afterEmpiLatch = new PointcutLatch(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myPractitioner = createPractitionerAndUpdateLinks(new Practitioner());
|
||||
myPractitionerId = new StringType(myPractitioner.getIdElement().getValue());
|
||||
myPractitionerPerson = getPersonFromTarget(myPractitioner);
|
||||
myPractitionerPersonId = new StringType(myPractitionerPerson.getIdElement().getValue());
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED, afterEmpiLatch);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws IOException {
|
||||
myInterceptorService.unregisterInterceptor(afterEmpiLatch);
|
||||
super.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnAllPractitioners() throws InterruptedException {
|
||||
StringType criteria = null;
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
|
||||
afterEmpiLatch.runWithExpectedCount(1, () -> myEmpiProviderR4.empiBatchPractitionerType(criteria, null));
|
||||
assertLinkCount(1);
|
||||
}
|
||||
@Test
|
||||
public void testBatchRunOnSpecificPractitioner() throws InterruptedException {
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
afterEmpiLatch.runWithExpectedCount(1, () -> myEmpiProviderR4.empiBatchPractitionerInstance(myPractitioner.getIdElement(), null));
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnNonExistentSpecificPractitioner() {
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
try {
|
||||
myEmpiProviderR4.empiBatchPractitionerInstance(new IdType("Practitioner/999"), null);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e){}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnAllPatients() throws InterruptedException {
|
||||
assertLinkCount(2);
|
||||
StringType criteria = null;
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
afterEmpiLatch.runWithExpectedCount(1, () -> myEmpiProviderR4.empiBatchPatientType(criteria, null));
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnSpecificPatient() throws InterruptedException {
|
||||
assertLinkCount(2);
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
afterEmpiLatch.runWithExpectedCount(1, () -> myEmpiProviderR4.empiBatchPatientInstance(myPatient.getIdElement(), null));
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnNonExistentSpecificPatient() {
|
||||
assertLinkCount(2);
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
try {
|
||||
myEmpiProviderR4.empiBatchPatientInstance(new IdType("Patient/999"), null);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e){}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnAllTypes() throws InterruptedException {
|
||||
assertLinkCount(2);
|
||||
StringType criteria = new StringType("");
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
afterEmpiLatch.runWithExpectedCount(2, () -> {
|
||||
myEmpiProviderR4.empiBatchOnAllTargets(criteria, null);
|
||||
});
|
||||
assertLinkCount(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchRunOnAllTypesWithInvalidCriteria() {
|
||||
assertLinkCount(2);
|
||||
StringType criteria = new StringType("death-date=2020-06-01");
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
|
||||
try {
|
||||
myEmpiProviderR4.empiBatchPractitionerType(criteria, null);
|
||||
fail();
|
||||
} catch(InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), is(equalTo("Failed to parse match URL[death-date=2020-06-01] - Resource type Practitioner does not have a parameter with name: death-date")));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
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.Parameters;
|
||||
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.Reference;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class EmpiProviderClearLinkR4Test extends BaseLinkR4Test {
|
||||
|
||||
|
||||
protected Practitioner myPractitioner;
|
||||
protected StringType myPractitionerId;
|
||||
protected Person myPractitionerPerson;
|
||||
protected StringType myPractitionerPersonId;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myPractitioner = createPractitionerAndUpdateLinks(new Practitioner());
|
||||
myPractitionerId = new StringType(myPractitioner.getIdElement().getValue());
|
||||
myPractitionerPerson = getPersonFromTarget(myPractitioner);
|
||||
myPractitionerPersonId = new StringType(myPractitionerPerson.getIdElement().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearAllLinks() {
|
||||
assertLinkCount(2);
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
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(2);
|
||||
Person read = myPersonDao.read(new IdDt(myPersonId.getValueAsString()).toVersionless());
|
||||
assertThat(read, is(notNullValue()));
|
||||
myEmpiProviderR4.clearEmpiLinks(new StringType("Patient"));
|
||||
assertNoPatientLinksExist();
|
||||
try {
|
||||
myPersonDao.read(new IdDt(myPersonId.getValueAsString()).toVersionless());
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void testPersonsWithMultipleHistoricalVersionsCanBeDeleted() {
|
||||
createPatientAndUpdateLinks(buildJanePatient());
|
||||
createPatientAndUpdateLinks(buildJanePatient());
|
||||
createPatientAndUpdateLinks(buildJanePatient());
|
||||
createPatientAndUpdateLinks(buildJanePatient());
|
||||
Patient patientAndUpdateLinks = createPatientAndUpdateLinks(buildJanePatient());
|
||||
Person person = getPersonFromTarget(patientAndUpdateLinks);
|
||||
assertThat(person, is(notNullValue()));
|
||||
myEmpiProviderR4.clearEmpiLinks(null);
|
||||
assertNoPatientLinksExist();
|
||||
person = getPersonFromTarget(patientAndUpdateLinks);
|
||||
assertThat(person, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersonWithLinksToOtherPersonsCanBeDeleted() {
|
||||
createPatientAndUpdateLinks(buildJanePatient());
|
||||
Patient patientAndUpdateLinks1 = createPatientAndUpdateLinks(buildJanePatient());
|
||||
Patient patientAndUpdateLinks = createPatientAndUpdateLinks(buildPaulPatient());
|
||||
|
||||
Person personFromTarget = getPersonFromTarget(patientAndUpdateLinks);
|
||||
Person personFromTarget2 = getPersonFromTarget(patientAndUpdateLinks1);
|
||||
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
|
||||
Parameters parameters = 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
|
||||
public void testClearPractitionerLinks() {
|
||||
assertLinkCount(2);
|
||||
Person read = myPersonDao.read(new IdDt(myPractitionerPersonId.getValueAsString()).toVersionless());
|
||||
assertThat(read, is(notNullValue()));
|
||||
myEmpiProviderR4.clearEmpiLinks(new StringType("Practitioner"));
|
||||
assertNoPractitionerLinksExist();
|
||||
try {
|
||||
myPersonDao.read(new IdDt(myPractitionerPersonId.getValueAsString()).toVersionless());
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearInvalidTargetType() {
|
||||
try {
|
||||
myEmpiProviderR4.clearEmpiLinks(new StringType("Observation"));
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), is(equalTo("$empi-clear does not support resource type: Observation")));
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected List<EmpiLink> getPractitionerLinks() {
|
||||
return myEmpiLinkDaoSvc.findEmpiLinksByTarget(myPractitioner);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
import ca.uhn.fhir.empi.api.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||
import ca.uhn.test.concurrency.PointcutLatch;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
class EmpiBatchSvcImplTest extends BaseEmpiR4Test {
|
||||
|
||||
@Autowired
|
||||
IEmpiBatchSvc myEmpiBatchSvc;
|
||||
|
||||
@Autowired
|
||||
IInterceptorService myInterceptorService;
|
||||
|
||||
PointcutLatch afterEmpiLatch = new PointcutLatch(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED);
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myInterceptorService.registerAnonymousInterceptor(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED, afterEmpiLatch);
|
||||
}
|
||||
@AfterEach
|
||||
public void after() throws IOException {
|
||||
myInterceptorService.unregisterInterceptor(afterEmpiLatch);
|
||||
afterEmpiLatch.clear();
|
||||
super.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpiBatchRunWorksOverMultipleTargetTypes() throws InterruptedException {
|
||||
|
||||
for (int i =0; i < 10; i++) {
|
||||
createPatient(buildJanePatient());
|
||||
}
|
||||
|
||||
for(int i = 0; i< 10; i++) {
|
||||
createPractitioner(buildPractitionerWithNameAndId("test", "id"));
|
||||
}
|
||||
|
||||
assertLinkCount(0);
|
||||
|
||||
//SUT
|
||||
afterEmpiLatch.runWithExpectedCount(20, () -> myEmpiBatchSvc.runEmpiOnAllTargetTypes(null));
|
||||
|
||||
assertLinkCount(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpiBatchOnPatientType() throws Exception {
|
||||
|
||||
for (int i =0; i < 10; i++) {
|
||||
createPatient(buildPatientWithNameAndId("test", "id"));
|
||||
}
|
||||
|
||||
assertLinkCount(0);
|
||||
|
||||
//SUT
|
||||
afterEmpiLatch.runWithExpectedCount(10, () -> myEmpiBatchSvc.runEmpiOnTargetType("Patient", null));
|
||||
|
||||
assertLinkCount(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpiBatchOnPractitionerType() throws Exception {
|
||||
|
||||
for (int i =0; i < 10; i++) {
|
||||
createPractitioner(buildPractitionerWithNameAndId("test", "id"));
|
||||
}
|
||||
|
||||
assertLinkCount(0);
|
||||
|
||||
//SUT
|
||||
afterEmpiLatch.runWithExpectedCount(10, () -> myEmpiBatchSvc.runEmpiOnAllTargetTypes(null));
|
||||
|
||||
assertLinkCount(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpiOnTargetTypeWithCriteria() throws InterruptedException {
|
||||
createPatient(buildPatientWithNameIdAndBirthday("gary", "gary_id", new Date()));
|
||||
createPatient(buildPatientWithNameIdAndBirthday("john", "john_id", DateUtils.addDays(new Date(), -300)));
|
||||
|
||||
assertLinkCount(0);
|
||||
|
||||
//SUT
|
||||
afterEmpiLatch.runWithExpectedCount(1, () -> myEmpiBatchSvc.runEmpiOnAllTargetTypes("Patient?name=gary"));
|
||||
|
||||
assertLinkCount(1);
|
||||
}
|
||||
}
|
|
@ -145,7 +145,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
createEmpiLink(myFromPerson, myTargetPatient1);
|
||||
|
||||
mergePersons();
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPerson(myToPerson);
|
||||
assertEquals(1, links.size());
|
||||
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1)));
|
||||
assertEquals(1, myToPerson.getLink().size());
|
||||
|
@ -156,7 +156,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
createEmpiLink(myToPerson, myTargetPatient1);
|
||||
|
||||
mergePersons();
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPerson(myToPerson);
|
||||
assertEquals(1, links.size());
|
||||
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1)));
|
||||
assertEquals(1, myToPerson.getLink().size());
|
||||
|
@ -172,7 +172,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
createEmpiLink(myToPerson, myTargetPatient1);
|
||||
|
||||
mergePersons();
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPerson(myToPerson);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
createEmpiLink(myToPerson, myTargetPatient1);
|
||||
|
||||
mergePersons();
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPerson(myToPerson);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
saveLink(toLink);
|
||||
|
||||
mergePersons();
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
|
||||
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPerson(myToPerson);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
|
||||
}
|
||||
|
|
|
@ -102,7 +102,6 @@ public class AddForeignKeyTask extends BaseTableColumnTask {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -138,6 +138,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
|||
empiLink.addColumn("20200715.5", "SCORE").nullable().type(ColumnTypeEnum.FLOAT);
|
||||
|
||||
init510_20200725();
|
||||
|
||||
//EMPI Target Type
|
||||
empiLink.addColumn("20200727.1","TARGET_TYPE").nullable().type(ColumnTypeEnum.STRING, EmpiLink.TARGET_TYPE_LENGTH);
|
||||
}
|
||||
|
||||
protected void init510_20200725() {
|
||||
|
|
|
@ -160,7 +160,6 @@ public class ResourceModifiedMessage extends BaseResourceMessage implements IRes
|
|||
UPDATE,
|
||||
DELETE,
|
||||
MANUALLY_TRIGGERED
|
||||
|
||||
}
|
||||
|
||||
private static boolean payloadContainsNoPlaceholderReferences(FhirContext theCtx, IBaseResource theNewPayload) {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
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%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
public interface IEmpiBatchSvc {
|
||||
|
||||
/**
|
||||
* Submit all eligible resources for EMPI processing.
|
||||
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for EMPI processing.
|
||||
* NOTE:
|
||||
* When using this function, the criteria supplied must be valid for all EMPI types. e.g. , if you
|
||||
* run this with the criteria birthDate=1990-06-28, it will fail, as Practitioners do not have a birthday.
|
||||
* Use with caution.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
long runEmpiOnAllTargetTypes(String theCriteria);
|
||||
|
||||
/**
|
||||
* Given a type and a search criteria, submit all found resources for EMPI processing.
|
||||
*
|
||||
* @param theTargetType the resource type that you wish to execute a search over for submission to EMPI.
|
||||
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for EMPI processing..
|
||||
* @return the number of resources submitted for EMPI processing.
|
||||
*/
|
||||
long runEmpiOnTargetType(String theTargetType, String theCriteria);
|
||||
|
||||
/**
|
||||
* Convenience method that calls {@link #runEmpiOnTargetType(String, String)} with the type pre-populated.
|
||||
*
|
||||
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for EMPI processing.
|
||||
* @return the number of resources submitted for EMPI processing.
|
||||
*/
|
||||
long runEmpiOnPractitionerType(String theCriteria);
|
||||
|
||||
/**
|
||||
* Convenience method that calls {@link #runEmpiOnTargetType(String, String)} with the type pre-populated.
|
||||
*
|
||||
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for EMPI processing.
|
||||
* @return the number of resources submitted for EMPI processing.
|
||||
*/
|
||||
long runEmpiOnPatientType(String theCriteria);
|
||||
|
||||
/**
|
||||
* Given an ID and a target type valid for EMPI, manually submit the given ID for EMPI processing.
|
||||
* @param theId the ID of the resource to process for EMPI.
|
||||
* @return the constant `1`, as if this function returns successfully, it will have processed one resource for EMPI.
|
||||
*/
|
||||
long runEmpiOnTarget(IIdType theId);
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
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%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public interface IEmpiChannelSubmitterSvc {
|
||||
|
||||
/**
|
||||
* Given an IBaseResource, submit it to the EMPI channel for processing.
|
||||
*
|
||||
* @param theResource the {@link IBaseResource} that should have EMPI processing applied to it.
|
||||
*/
|
||||
void submitResourceToEmpiChannel(IBaseResource theResource);
|
||||
}
|
|
@ -28,4 +28,5 @@ public interface IEmpiLinkQuerySvc {
|
|||
IBaseParameters queryLinks(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiContext);
|
||||
|
||||
IBaseParameters getPossibleDuplicates(EmpiTransactionContext theEmpiContext);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
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 IEmpiResetSvc {
|
||||
|
||||
/**
|
||||
* Given a resource type, delete the underlying EMPI links, and their related person objects.
|
||||
*
|
||||
* @param theResourceType The type of resources
|
||||
*
|
||||
* @return the count of deleted EMPI links
|
||||
*/
|
||||
long expungeAllEmpiLinksOfTargetType(String theResourceType);
|
||||
|
||||
/**
|
||||
* Delete all EMPI links, and their related Person objects.
|
||||
*
|
||||
*
|
||||
* @return the count of deleted EMPI links
|
||||
*/
|
||||
long removeAllEmpiLinks();
|
||||
}
|
|
@ -23,10 +23,13 @@ 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.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -34,11 +37,14 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.validation.IResourceLoader;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.DecimalType;
|
||||
import org.hl7.fhir.dstu3.model.InstantType;
|
||||
import org.hl7.fhir.dstu3.model.Parameters;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Person;
|
||||
import org.hl7.fhir.dstu3.model.Practitioner;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
@ -52,6 +58,8 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider {
|
|||
private final IEmpiPersonMergerSvc myPersonMergerSvc;
|
||||
private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc;
|
||||
private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc;
|
||||
private final IEmpiResetSvc myEmpiResetSvc;
|
||||
private final IEmpiBatchSvc myEmpiBatchSvc;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -59,12 +67,14 @@ 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, IEmpiResetSvc theEmpiResetSvc, IEmpiBatchSvc theEmpiBatchSvc) {
|
||||
super(theFhirContext, theResourceLoader);
|
||||
myEmpiMatchFinderSvc = theEmpiMatchFinderSvc;
|
||||
myPersonMergerSvc = thePersonMergerSvc;
|
||||
myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc;
|
||||
myEmpiLinkQuerySvc = theEmpiLinkQuerySvc;
|
||||
myEmpiResetSvc = theEmpiResetSvc;
|
||||
myEmpiBatchSvc = theEmpiBatchSvc;
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class)
|
||||
|
@ -149,4 +159,88 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider {
|
|||
|
||||
return (Parameters) myEmpiLinkUpdaterSvc.notDuplicatePerson(person, target, createEmpiContext(theRequestDetails));
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= DecimalType.class)
|
||||
})
|
||||
public Parameters empiBatchOnAllTargets(
|
||||
@OperationParam(name= ProviderConstants.EMPI_BATCH_RUN_CRITERIA,min = 0 , max = 1) StringType theCriteria,
|
||||
ServletRequestDetails theRequestDetails) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnAllTargetTypes(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
private String convertCriteriaToString(StringType theCriteria) {
|
||||
return theCriteria == null ? null : theCriteria.getValueAsString();
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.EMPI_CLEAR, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= DecimalType.class)
|
||||
})
|
||||
public Parameters clearEmpiLinks(@OperationParam(name=ProviderConstants.EMPI_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType) {
|
||||
long resetCount;
|
||||
if (theTargetType == null || StringUtils.isBlank(theTargetType.getValue())) {
|
||||
resetCount = myEmpiResetSvc.removeAllEmpiLinks();
|
||||
} else {
|
||||
resetCount = myEmpiResetSvc.expungeAllEmpiLinksOfTargetType(theTargetType.getValueNotNull());
|
||||
}
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter().setName(ProviderConstants.OPERATION_EMPI_CLEAR_OUT_PARAM_DELETED_COUNT)
|
||||
.setValue(new DecimalType(resetCount));
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Patient.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
|
||||
})
|
||||
public Parameters empiBatchPatientInstance(
|
||||
@IdParam IIdType theIdParam,
|
||||
RequestDetails theRequest) {
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnTarget(theIdParam);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Patient.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
|
||||
})
|
||||
public Parameters empiBatchPatientType(
|
||||
@OperationParam(name = ProviderConstants.EMPI_BATCH_RUN_CRITERIA) StringType theCriteria,
|
||||
RequestDetails theRequest) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnPatientType(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Practitioner.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
|
||||
})
|
||||
public Parameters empiBatchPractitionerInstance(
|
||||
@IdParam IIdType theIdParam,
|
||||
RequestDetails theRequest) {
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnTarget(theIdParam);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Practitioner.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = DecimalType.class)
|
||||
})
|
||||
public Parameters empiBatchPractitionerType(
|
||||
@OperationParam(name = ProviderConstants.EMPI_BATCH_RUN_CRITERIA) StringType theCriteria,
|
||||
RequestDetails theRequest) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnPractitionerType(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build the out-parameters for all batch EMPI operations.
|
||||
*/
|
||||
private Parameters buildEmpiOutParametersWithCount(long theCount) {
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter()
|
||||
.setName(ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT)
|
||||
.setValue(new DecimalType(theCount));
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.empi.provider;
|
|||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.empi.api.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||
|
@ -47,14 +49,18 @@ public class EmpiProviderLoader {
|
|||
private IEmpiLinkQuerySvc myEmpiLinkQuerySvc;
|
||||
@Autowired
|
||||
private IResourceLoader myResourceLoader;
|
||||
@Autowired
|
||||
private IEmpiResetSvc myEmpiResetSvc;
|
||||
@Autowired
|
||||
private IEmpiBatchSvc myEmpiBatchSvc;
|
||||
|
||||
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, myEmpiResetSvc, myEmpiBatchSvc));
|
||||
break;
|
||||
case R4:
|
||||
myResourceProviderFactory.addSupplier(() -> new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader));
|
||||
myResourceProviderFactory.addSupplier(() -> new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader, myEmpiResetSvc, myEmpiBatchSvc));
|
||||
break;
|
||||
default:
|
||||
throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion());
|
||||
|
|
|
@ -23,10 +23,13 @@ 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.IEmpiBatchSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiResetSvc;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -34,13 +37,17 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.validation.IResourceLoader;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.DecimalType;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.hl7.fhir.r4.model.IntegerType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
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.Resource;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
|
||||
|
@ -52,6 +59,8 @@ public class EmpiProviderR4 extends BaseEmpiProvider {
|
|||
private final IEmpiPersonMergerSvc myPersonMergerSvc;
|
||||
private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc;
|
||||
private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc;
|
||||
private final IEmpiResetSvc myEmpiExpungeSvc;
|
||||
private final IEmpiBatchSvc myEmpiBatchSvc;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -59,12 +68,14 @@ 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, IEmpiResetSvc theEmpiExpungeSvc, IEmpiBatchSvc theEmpiBatchSvc) {
|
||||
super(theFhirContext, theResourceLoader);
|
||||
myEmpiMatchFinderSvc = theEmpiMatchFinderSvc;
|
||||
myPersonMergerSvc = thePersonMergerSvc;
|
||||
myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc;
|
||||
myEmpiLinkQuerySvc = theEmpiLinkQuerySvc;
|
||||
myEmpiExpungeSvc = theEmpiExpungeSvc;
|
||||
myEmpiBatchSvc = theEmpiBatchSvc;
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class)
|
||||
|
@ -117,6 +128,22 @@ public class EmpiProviderR4 extends BaseEmpiProvider {
|
|||
return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails));
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.EMPI_CLEAR, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type=DecimalType.class)
|
||||
})
|
||||
public Parameters clearEmpiLinks(@OperationParam(name=ProviderConstants.EMPI_CLEAR_TARGET_TYPE, min = 0, max = 1) StringType theTargetType) {
|
||||
long resetCount;
|
||||
if (theTargetType == null || StringUtils.isBlank(theTargetType.getValue())) {
|
||||
resetCount = myEmpiExpungeSvc.removeAllEmpiLinks();
|
||||
} else {
|
||||
resetCount = myEmpiExpungeSvc.expungeAllEmpiLinksOfTargetType(theTargetType.getValueNotNull());
|
||||
}
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter().setName(ProviderConstants.OPERATION_EMPI_CLEAR_OUT_PARAM_DELETED_COUNT)
|
||||
.setValue(new DecimalType(resetCount));
|
||||
return 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,
|
||||
|
@ -149,4 +176,72 @@ public class EmpiProviderR4 extends BaseEmpiProvider {
|
|||
|
||||
return (Parameters) myEmpiLinkUpdaterSvc.notDuplicatePerson(person, target, createEmpiContext(theRequestDetails));
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type= IntegerType.class)
|
||||
})
|
||||
public Parameters empiBatchOnAllTargets(
|
||||
@OperationParam(name= ProviderConstants.EMPI_BATCH_RUN_CRITERIA,min = 0 , max = 1) StringType theCriteria,
|
||||
ServletRequestDetails theRequestDetails) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnAllTargetTypes(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
private String convertCriteriaToString(StringType theCriteria) {
|
||||
return theCriteria == null ? null : theCriteria.getValueAsString();
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Patient.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
|
||||
})
|
||||
public Parameters empiBatchPatientInstance(
|
||||
@IdParam IIdType theIdParam,
|
||||
RequestDetails theRequest) {
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnTarget(theIdParam);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Patient.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
|
||||
})
|
||||
public Parameters empiBatchPatientType(
|
||||
@OperationParam(name = ProviderConstants.EMPI_BATCH_RUN_CRITERIA) StringType theCriteria,
|
||||
RequestDetails theRequest) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnPatientType(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Practitioner.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
|
||||
})
|
||||
public Parameters empiBatchPractitionerInstance(
|
||||
@IdParam IIdType theIdParam,
|
||||
RequestDetails theRequest) {
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnTarget(theIdParam);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
@Operation(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN, idempotent = false, type = Practitioner.class, returnParameters = {
|
||||
@OperationParam(name = ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT, type = IntegerType.class)
|
||||
})
|
||||
public Parameters empiBatchPractitionerType(
|
||||
@OperationParam(name = ProviderConstants.EMPI_BATCH_RUN_CRITERIA) StringType theCriteria,
|
||||
RequestDetails theRequest) {
|
||||
String criteria = convertCriteriaToString(theCriteria);
|
||||
long submittedCount = myEmpiBatchSvc.runEmpiOnPractitionerType(criteria);
|
||||
return buildEmpiOutParametersWithCount(submittedCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build the out-parameters for all batch EMPI operations.
|
||||
*/
|
||||
private Parameters buildEmpiOutParametersWithCount(long theCount) {
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter()
|
||||
.setName(ProviderConstants.OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT)
|
||||
.setValue(new DecimalType(theCount));
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,4 +81,11 @@ 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";
|
||||
public static final String OPERATION_EMPI_BATCH_RUN = "$empi-submit";
|
||||
public static final String EMPI_BATCH_RUN_CRITERIA= "criteria" ;
|
||||
public static final String OPERATION_EMPI_BATCH_RUN_OUT_PARAM_SUBMIT_COUNT = "submitted" ;
|
||||
public static final String OPERATION_EMPI_CLEAR_OUT_PARAM_DELETED_COUNT = "deleted";
|
||||
}
|
||||
|
|
|
@ -67,6 +67,12 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
|
|||
myPointcut = null;
|
||||
}
|
||||
|
||||
public void runWithExpectedCount(int theExpectedCount, Runnable r) throws InterruptedException {
|
||||
this.setExpectedCount(theExpectedCount);
|
||||
r.run();
|
||||
this.awaitExpected();
|
||||
}
|
||||
|
||||
public long getLastInvoke() {
|
||||
return myLastInvoke.get();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue