rebuild golden resource from survivorship on link edit (#5250)

* adding rebuilding option for survivorship

* rebuild survivorship resources

* cleanup

* configuring beans

* fixing tests

* cleanup

* spotless

* import fixing

* cleaning up

* fixing tests

* fixing tests

* version bump

* undo change

---------

Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-MacBook-Pro.local>
Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
This commit is contained in:
TipzCM 2023-09-12 23:03:24 -04:00 committed by GitHub
parent 2da8aafad0
commit f97eadadc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 943 additions and 300 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,13 @@
---
type: add
issue: 5236
title: "Previously, when updating an MDM link to NO_MATCH,
the golden resource involved would maintain its previous
values, as defined by survivorship service.
This would result in out-of-date golden resources with
data that might not be accurate anymore.
Now, when a link is changed to NO_MATCH, golden resources
will be rebuilt from the ground up using the MDM survivorship
service, and the set of links/source resources available at the
time of update.
"

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -70,14 +70,11 @@ import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.dao.mdm.JpaMdmLinkImplFactory;
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkDaoJpaImpl;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator;
import ca.uhn.fhir.jpa.delete.DeleteConflictFinderService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry;
import ca.uhn.fhir.jpa.graphql.DaoRegistryGraphQLStorageServices;
@ -171,9 +168,6 @@ import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.PersistenceContextProvider;
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.dao.IMdmLinkImplFactory;
import ca.uhn.fhir.mdm.svc.MdmLinkExpandSvc;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.IDeleteExpungeJobSubmitter;
@ -221,7 +215,8 @@ import javax.annotation.Nullable;
JpaBulkExportConfig.class,
SearchConfig.class,
PackageLoaderConfig.class,
EnversAuditConfig.class
EnversAuditConfig.class,
MdmJpaConfig.class
})
public class JpaConfig {
public static final String JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain";
@ -480,11 +475,6 @@ public class JpaConfig {
return new RequestTenantPartitionInterceptor();
}
@Bean
public MdmLinkExpandSvc mdmLinkExpandSvc() {
return new MdmLinkExpandSvc();
}
@Bean
@Lazy
public TerminologyUploaderProvider terminologyUploaderProvider() {
@ -858,16 +848,6 @@ public class JpaConfig {
return new ObservationLastNIndexPersistSvc();
}
@Bean
public IMdmLinkDao<JpaPid, MdmLink> mdmLinkDao() {
return new MdmLinkDaoJpaImpl();
}
@Bean
IMdmLinkImplFactory<MdmLink> mdmLinkImplFactory() {
return new JpaMdmLinkImplFactory();
}
@Bean
@Scope("prototype")
public PersistenceContextProvider persistenceContextProvider() {

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.svc.IDeleteExpungeSvc;
import ca.uhn.fhir.jpa.api.svc.IMdmClearHelperSvc;
import ca.uhn.fhir.jpa.bulk.mdm.MdmClearHelperSvcImpl;
import ca.uhn.fhir.jpa.dao.mdm.JpaMdmLinkImplFactory;
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkDaoJpaImpl;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.dao.IMdmLinkImplFactory;
import ca.uhn.fhir.mdm.svc.MdmLinkExpandSvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MdmJpaConfig {
@Bean
public MdmLinkExpandSvc mdmLinkExpandSvc() {
return new MdmLinkExpandSvc();
}
@Bean
public IMdmLinkDao<JpaPid, MdmLink> mdmLinkDao() {
return new MdmLinkDaoJpaImpl();
}
@Bean
public IMdmLinkImplFactory<MdmLink> mdmLinkImplFactory() {
return new JpaMdmLinkImplFactory();
}
@Bean
public IMdmClearHelperSvc<JpaPid> helperSvc(IDeleteExpungeSvc<JpaPid> theDeleteExpungeSvc) {
return new MdmClearHelperSvcImpl(theDeleteExpungeSvc);
}
}

View File

@ -28,12 +28,12 @@ import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.EnversRevision;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.model.MdmPidTuple;
import ca.uhn.fhir.rest.api.SortOrderEnum;
@ -75,13 +75,13 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.GOLDEN_RESOURCE_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.GOLDEN_RESOURCE_PID_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.LINK_SOURCE_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.MATCH_RESULT_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.PARTITION_ID_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.RESOURCE_TYPE_NAME;
import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.SOURCE_PID_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.GOLDEN_RESOURCE_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.GOLDEN_RESOURCE_PID_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.LINK_SOURCE_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.MATCH_RESULT_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.PARTITION_ID_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.RESOURCE_TYPE_NAME;
import static ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters.SOURCE_PID_NAME;
public class MdmLinkDaoJpaImpl implements IMdmLinkDao<JpaPid, MdmLink> {
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkDaoJpaImpl.class);
@ -380,7 +380,15 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao<JpaPid, MdmLink> {
if (!theMdmHistorySearchParameters.getGoldenResourceIds().isEmpty()
&& !theMdmHistorySearchParameters.getSourceIds().isEmpty()) {
goldenResourceAndOrResourceIdCriterion = AuditEntity.or(goldenResourceIdCriterion, resourceIdCriterion);
if (theMdmHistorySearchParameters.getParameterJoinType() == MdmHistorySearchParameters.JoinType.AND) {
// 'and' the source and golden ids
goldenResourceAndOrResourceIdCriterion =
AuditEntity.and(goldenResourceIdCriterion, resourceIdCriterion);
} else {
// default is 'or'
goldenResourceAndOrResourceIdCriterion =
AuditEntity.or(goldenResourceIdCriterion, resourceIdCriterion);
}
} else if (!theMdmHistorySearchParameters.getGoldenResourceIds().isEmpty()) {
goldenResourceAndOrResourceIdCriterion = goldenResourceIdCriterion;
} else if (!theMdmHistorySearchParameters.getSourceIds().isEmpty()) {

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -41,7 +41,6 @@ import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
import ca.uhn.fhir.jpa.mdm.svc.MdmModelConverterSvcImpl;
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc;
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc;
import ca.uhn.fhir.jpa.mdm.svc.MdmSurvivorshipSvcImpl;
import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateSearcher;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByEidSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByExampleSvc;
@ -59,7 +58,6 @@ import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.batch2.MdmBatch2Config;
import ca.uhn.fhir.mdm.blocklist.svc.IBlockListRuleProvider;
import ca.uhn.fhir.mdm.blocklist.svc.IBlockRuleEvaluationSvc;
@ -72,7 +70,6 @@ import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
import ca.uhn.fhir.mdm.provider.MdmProviderLoader;
import ca.uhn.fhir.mdm.svc.MdmSearchParamSvc;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.validation.IResourceLoader;
@ -83,8 +80,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({MdmCommonConfig.class, MdmBatch2Config.class})
@Import({MdmCommonConfig.class, MdmSurvivorshipConfig.class, MdmBatch2Config.class})
public class MdmConsumerConfig {
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
@Bean
@ -92,11 +90,6 @@ public class MdmConsumerConfig {
return new MdmStorageInterceptor();
}
@Bean
IMdmSurvivorshipService mdmSurvivorshipService() {
return new MdmSurvivorshipSvcImpl();
}
@Bean
MdmQueueConsumerLoader mdmQueueConsumerLoader(
IChannelFactory theChannelFactory, IMdmSettings theMdmSettings, MdmMessageHandler theMdmMessageHandler) {
@ -139,11 +132,6 @@ public class MdmConsumerConfig {
return new MdmLinkSvcImpl();
}
@Bean
GoldenResourceHelper goldenResourceHelper(FhirContext theFhirContext) {
return new GoldenResourceHelper(theFhirContext);
}
@Bean
MessageHelper messageHelper(IMdmSettings theMdmSettings, FhirContext theFhirContext) {
return new MessageHelper(theMdmSettings, theFhirContext);

View File

@ -0,0 +1,52 @@
package ca.uhn.fhir.jpa.mdm.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.svc.MdmSurvivorshipSvcImpl;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.mdm.util.MdmPartitionHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MdmSurvivorshipConfig {
@Autowired
protected FhirContext myFhirContext;
@Autowired
protected DaoRegistry myDaoRegistry;
@Autowired
private IMdmSettings myMdmSettings;
@Autowired
private EIDHelper myEIDHelper;
@Autowired
private MdmPartitionHelper myMdmPartitionHelper;
@Autowired
private IMdmLinkQuerySvc myMdmLinkQuerySvc;
@Autowired
private IIdHelperService<?> myIIdHelperService;
@Bean
public IMdmSurvivorshipService mdmSurvivorshipService() {
return new MdmSurvivorshipSvcImpl(
myFhirContext, goldenResourceHelper(), myDaoRegistry, myMdmLinkQuerySvc, myIIdHelperService);
}
@Bean
public GoldenResourceHelper goldenResourceHelper() {
// do not make this depend on IMdmSurvivorshipService
return new GoldenResourceHelper(myFhirContext, myMdmSettings, myEIDHelper, myMdmPartitionHelper);
}
}

View File

@ -24,12 +24,12 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.dao.MdmLinkFactory;
import ca.uhn.fhir.mdm.log.Logs;
@ -282,6 +282,7 @@ public class MdmLinkDaoSvc<P extends IResourcePersistentId, M extends IMdmLink<P
Example<M> example = Example.of(exampleLink);
return myMdmLinkDao.findOne(example);
}
/**
* Delete a given {@link IMdmLink}. Note that this does not clear out the Golden resource.
* It is a simple entity delete.

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
@ -81,6 +82,9 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
@Autowired
IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private IMdmSurvivorshipService myMdmSurvivorshipService;
@Override
@Transactional
public IAnyResource mergeGoldenResources(MdmMergeGoldenResourcesParams theParams) {
@ -105,7 +109,8 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
.getResource();
} else {
myGoldenResourceHelper.mergeIndentifierFields(fromGoldenResource, toGoldenResource, mdmTransactionContext);
myGoldenResourceHelper.mergeNonIdentiferFields(fromGoldenResource, toGoldenResource, mdmTransactionContext);
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
fromGoldenResource, toGoldenResource, mdmTransactionContext);
// Save changes to the golden resource
myMdmResourceDaoSvc.upsertGoldenResource(toGoldenResource, resourceType);
}

View File

@ -34,10 +34,10 @@ import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.batch2.clear.MdmClearAppCtx;
import ca.uhn.fhir.mdm.batch2.clear.MdmClearJobParameters;
import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitAppCtx;

View File

@ -180,8 +180,8 @@ public class MdmEidUpdateService {
log(
theMdmTransactionContext,
"Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newGoldenResource =
myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource, theMdmTransactionContext);
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
theResource, theMdmTransactionContext, myMdmSurvivorshipService);
myMdmLinkSvc.updateLink(
newGoldenResource,

View File

@ -22,12 +22,12 @@ package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;

View File

@ -141,16 +141,7 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
// Add partition for the mdm link if it doesn't exist
RequestPartitionId goldenResourcePartitionId =
(RequestPartitionId) goldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (goldenResourcePartitionId != null
&& goldenResourcePartitionId.hasPartitionIds()
&& goldenResourcePartitionId.getFirstPartitionIdOrNull() != null
&& (mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
mdmLink.setPartitionId(new PartitionablePartitionId(
goldenResourcePartitionId.getFirstPartitionIdOrNull(),
goldenResourcePartitionId.getPartitionDate()));
}
addPartitioninfoForLinkIfNecessary(goldenResource, mdmLink);
myMdmLinkDaoSvc.save(mdmLink);
if (matchResult == MdmMatchResultEnum.MATCH) {
@ -158,15 +149,38 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(sourceResource, goldenResource, mdmContext);
}
/**
* We use the versionless id
* because we call update on the goldenResource in 2 places:
* here and below where we rebuild goldenresources if we have set
* a link to NO_MATCH.
*
* This can be a problem when a source resource is deleted.
* then {@link MdmStorageInterceptor} will update all links
* connecting to any golden resource that was connected to the now deleted
* source resource to NO_MATCH before deleting orphaned golden resources.
*/
goldenResource.setId(goldenResource.getIdElement().toVersionless());
myMdmResourceDaoSvc.upsertGoldenResource(goldenResource, mdmContext.getResourceType());
if (matchResult == MdmMatchResultEnum.NO_MATCH) {
// We need to return no match for when a Golden Resource has already been found elsewhere
if (myMdmLinkDaoSvc
.getMdmLinksBySourcePidAndMatchResult(sourceResourceId, MdmMatchResultEnum.MATCH)
.isEmpty()) {
// Need to find a new Golden Resource to link this target to
/*
* link is broken. We need to do 2 things:
* * update links for the source resource (if no other golden resources exist, for instance)
* * rebuild the golden resource from scratch, using current survivorship rules
* and the current set of links
*/
List<?> links =
myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(sourceResourceId, MdmMatchResultEnum.MATCH);
if (links.isEmpty()) {
// No more links to source; Find a new Golden Resource to link this target to
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(sourceResource, mdmContext);
}
// with the link broken, the golden resource has delta info from a resource
// that is no longer matched to it; we need to remove this delta. But it's
// easier to just rebuild the resource from scratch using survivorship rules/current links
goldenResource =
myMdmSurvivorshipService.rebuildGoldenResourceWithSurvivorshipRules(goldenResource, mdmContext);
}
if (myInterceptorBroadcaster.hasHooks(Pointcut.MDM_POST_UPDATE_LINK)) {
@ -181,6 +195,19 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
return goldenResource;
}
private static void addPartitioninfoForLinkIfNecessary(IAnyResource goldenResource, IMdmLink mdmLink) {
RequestPartitionId goldenResourcePartitionId =
(RequestPartitionId) goldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (goldenResourcePartitionId != null
&& goldenResourcePartitionId.hasPartitionIds()
&& goldenResourcePartitionId.getFirstPartitionIdOrNull() != null
&& (mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
mdmLink.setPartitionId(new PartitionablePartitionId(
goldenResourcePartitionId.getFirstPartitionIdOrNull(),
goldenResourcePartitionId.getPartitionDate()));
}
}
/**
* When updating POSSIBLE_MATCH link to a MATCH we need to validate that a MATCH to a different golden resource
* doesn't exist, because a resource mustn't be a MATCH to more than one golden resource

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateStrategyEnum;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
@ -69,6 +70,9 @@ public class MdmMatchLinkSvc {
@Autowired
private IBlockRuleEvaluationSvc myBlockRuleEvaluationSvc;
@Autowired
private IMdmSurvivorshipService myMdmSurvivorshipService;
/**
* Given an MDM source (consisting of any supported MDM type), find a suitable Golden Resource candidate for them,
* or create one if one does not exist. Performs matching based on rules defined in mdm-rules.json.
@ -187,8 +191,8 @@ public class MdmMatchLinkSvc {
String.format(
"There were no matched candidates for MDM, creating a new %s Golden Resource.",
theResource.getIdElement().getResourceType()));
IAnyResource newGoldenResource =
myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theResource, theMdmTransactionContext);
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
theResource, theMdmTransactionContext, myMdmSurvivorshipService);
// TODO GGG :)
// 1. Get the right helper
// 2. Create source resource for the MDM source
@ -214,7 +218,7 @@ public class MdmMatchLinkSvc {
theMdmTransactionContext,
"Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
theTargetResource, theMdmTransactionContext);
theTargetResource, theMdmTransactionContext, myMdmSurvivorshipService);
myMdmLinkSvc.updateLink(
newGoldenResource,

View File

@ -1,65 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server - Master Data Management
* %%
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
* %%
* 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%
*/
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.util.TerserUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
@Autowired
private FhirContext myFhirContext;
/**
* Merges two golden resources by overwriting all field values on theGoldenResource param for CREATE_RESOURCE,
* UPDATE_RESOURCE, SUBMIT_RESOURCE_TO_MDM, UPDATE_LINK (when setting to MATCH) and MANUAL_MERGE_GOLDEN_RESOURCES.
* PID, identifiers and meta values are not affected by this operation.
*
* @param theTargetResource Target resource to retrieve fields from
* @param theGoldenResource Golden resource to merge fields into
* @param theMdmTransactionContext Current transaction context
* @param <T>
*/
@Override
public <T extends IBase> void applySurvivorshipRulesToGoldenResource(
T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
switch (theMdmTransactionContext.getRestOperation()) {
case MERGE_GOLDEN_RESOURCES:
TerserUtil.mergeFields(
myFhirContext,
(IBaseResource) theTargetResource,
(IBaseResource) theGoldenResource,
TerserUtil.EXCLUDE_IDS_AND_META);
break;
default:
TerserUtil.replaceFields(
myFhirContext,
(IBaseResource) theTargetResource,
(IBaseResource) theGoldenResource,
TerserUtil.EXCLUDE_IDS_AND_META);
break;
}
}
}

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.mdm.config.MdmConsumerConfig;
import ca.uhn.fhir.jpa.mdm.config.MdmSubmitterConfig;
import ca.uhn.fhir.jpa.mdm.config.TestMdmConfigR4;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.jpa.mdm.helper.MdmLinkHelper;
import ca.uhn.fhir.jpa.mdm.matcher.IsLinkedTo;
import ca.uhn.fhir.jpa.mdm.matcher.IsMatchedToAGoldenResource;
import ca.uhn.fhir.jpa.mdm.matcher.IsPossibleDuplicateOf;
@ -26,6 +27,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
@ -134,6 +136,11 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Autowired
protected IPartitionLookupSvc myPartitionLookupSvc;
@Autowired
protected IMdmLinkUpdaterSvc myMdmLinkUpdaterSvc;
@Autowired
protected MdmLinkHelper myLinkHelper;
@BeforeEach
public void beforeSetRequestDetails() {
myRequestDetails = new ServletRequestDetails(myInterceptorBroadcaster);

View File

@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.EnversRevision;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;

View File

@ -4,10 +4,10 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.mdm.api.IMdmSettings;
@ -11,6 +11,7 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
@ -147,10 +148,16 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
}
@Test
public void testUnlinkLink() {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
public void testUnlinkLink_usingOutOfDateResourceId_throwsResourceVersionConflict() {
IBaseResource resultantPatient = myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
materiallyChangeGoldenPatient();
/*
* updatating a link to NO_MATCH reruns survivorship rules
* (thus rebuilding the golden resource;
* which in this case is mySourcePatient).
* Thus we don't have to update the patient again to get the
* version out of sync
*/
try {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.batch2.clear.MdmClearStep;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;

View File

@ -4,7 +4,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;

View File

@ -1,11 +1,19 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.model.MdmCreateOrUpdateParams;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.ContactPoint;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
@ -14,6 +22,13 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.times;
import static org.slf4j.LoggerFactory.getLogger;
@ -35,6 +50,15 @@ public class MdmMatchLinkSvcSurvivorshipTest extends BaseMdmR4Test {
@Captor
ArgumentCaptor<MdmTransactionContext> myContext;
private IParser myParser;
@BeforeEach
public void before() throws Exception {
super.before();
myParser = myFhirContext.newJsonParser();
}
@Test
public void testSurvivorshipIsCalledOnMatchingToTheSameGoldenResource() {
// no candidates
@ -50,7 +74,87 @@ public class MdmMatchLinkSvcSurvivorshipTest extends BaseMdmR4Test {
verifySurvivorshipCalled(3);
}
@Test
public void testUpdateLinkToNoMatch_rebuildsGoldenResource() {
// setup
int resourceCount = 5;
Address address = new Address();
address.setCity("Toronto");
address.addLine("123 fake st");
ContactPoint tele = new ContactPoint();
tele.setSystem(ContactPoint.ContactPointSystem.PHONE);
tele.setValue("555-555-5555");
Date birthdate = new Date();
Patient r = null;
for (int i = 0; i < resourceCount; i++) {
// new patient with name
Patient p = buildJanePatient();
p.addName()
.addGiven("Jane")
.setFamily("Doe");
p.setTelecom(null);
p.setAddress(null);
p.setBirthDate(null);
/*
* We will add some different values to
* the final resource we create.
*
* We do this only on the final one, because
* the default IMdmSurvivorship rules is to replace
* all of the current values in a golden resource
* with all of the new values from the newly added
* one (ie, overwriting non-null values with null).
*/
if (i == resourceCount - 1) {
p.setTelecom(Arrays.asList(tele));
p.setAddress(Arrays.asList(address));
p.setBirthDate(birthdate);
}
r = createPatientAndUpdateLinks(p);
}
Optional<MdmLink> linkop = myMdmLinkDaoSvc.findMdmLinkBySource(r);
assertTrue(linkop.isPresent());
MdmLink link = linkop.get();
JpaPid gpid = link.getGoldenResourcePersistenceId();
Patient golden = myPatientDao.readByPid(gpid);
// we should have a link for each resource all linked
// to the same golden resource
assertEquals(resourceCount, myMdmLinkDaoSvc.findMdmLinksByGoldenResource(golden).size());
assertEquals(1, golden.getAddress().size());
assertEquals(1, golden.getTelecom().size());
assertEquals(r.getTelecom().get(0).getValue(), golden.getTelecom().get(0).getValue());
// test
// unmatch final link
MdmCreateOrUpdateParams params = new MdmCreateOrUpdateParams();
params.setGoldenResourceId(golden.getId());
params.setGoldenResource(golden);
params.setResourceId(r.getId());
params.setSourceResource(r);
params.setMdmContext(createContextForCreate("Patient"));
params.setMatchResult(MdmMatchResultEnum.NO_MATCH);
golden = (Patient) myMdmLinkUpdaterSvc.updateLink(params);
// verify
assertTrue(golden.getTelecom() == null || golden.getTelecom().isEmpty());
assertNull(golden.getBirthDate());
}
private void verifySurvivorshipCalled(int theNumberOfTimes) {
Mockito.verify(myMdmSurvivorshipService, times(theNumberOfTimes)).applySurvivorshipRulesToGoldenResource(myPatientCaptor.capture(), myPatientCaptor.capture(), myContext.capture());
Mockito.verify(myMdmSurvivorshipService, times(theNumberOfTimes))
.applySurvivorshipRulesToGoldenResource(
myPatientCaptor.capture(),
myPatientCaptor.capture(),
myContext.capture()
);
}
}

View File

@ -7,13 +7,13 @@ import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.mdm.config.BaseTestMdmConfig;
import ca.uhn.fhir.jpa.mdm.config.BlockListConfig;
import ca.uhn.fhir.jpa.mdm.helper.MdmLinkHelper;
import ca.uhn.fhir.jpa.mdm.helper.testmodels.MDMState;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
@ -91,10 +91,9 @@ public class MdmMatchLinkSvcTest {
@Autowired
private GoldenResourceHelper myGoldenResourceHelper;
@Autowired
private IMdmLinkUpdaterSvc myMdmLinkUpdaterSvc;
private IMdmSurvivorshipService myMdmSurvivorshipService;
@Autowired
private MdmLinkHelper myLinkHelper;
private IMdmLinkUpdaterSvc myMdmLinkUpdaterSvc;
@Test
public void testAddPatientLinksToNewGoldenResourceIfNoneFound() {
@ -479,7 +478,11 @@ public class MdmMatchLinkSvcTest {
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
//own individual GoldenResource for the purpose of this test.
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient2, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
janePatient2,
new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE),
myMdmSurvivorshipService
);
myMdmLinkSvc.updateLink(goldenResource, janePatient2, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertThat(janePatient, is(not(sameGoldenResourceAs(janePatient2))));
@ -593,7 +596,11 @@ public class MdmMatchLinkSvcTest {
public void testCreateGoldenResourceFromMdmTarget() {
// Create Use Case #2 - adding patient with no EID
Patient janePatient = buildJanePatient();
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
Patient janeGoldenResourcePatient = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
janePatient,
new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE),
myMdmSurvivorshipService
);
// golden record now contains HAPI-generated EID and HAPI tag
assertTrue(MdmResourceUtil.isMdmManaged(janeGoldenResourcePatient));
@ -769,8 +776,11 @@ public class MdmMatchLinkSvcTest {
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
//own individual GoldenResource for the purpose of this test.
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(janePatient2,
new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
IAnyResource goldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
janePatient2,
new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE),
myMdmSurvivorshipService
);
myMdmLinkSvc.updateLink(goldenResource, janePatient2, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH,
MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertThat(janePatient, is(not(sameGoldenResourceAs(janePatient2))));

View File

@ -0,0 +1,58 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MdmSurvivorshipSvcImplIT extends BaseMdmR4Test {
@Autowired
private IMdmSurvivorshipService myMdmSurvivorshipService;
@Test
public void testRulesOnCreate() {
Patient p1 = buildFrankPatient();
Patient p2 = new Patient();
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
assertFalse(p2.hasIdElement());
assertTrue(p2.getIdentifier().isEmpty());
assertTrue(p2.getMeta().isEmpty());
assertTrue(p1.getNameFirstRep().equalsDeep(p2.getNameFirstRep()));
assertNull(p2.getBirthDate());
assertEquals(p1.getTelecom().size(), p2.getTelecom().size());
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
}
@Test
public void testRulesOnMerge() {
Patient p1 = buildFrankPatient();
String p1Name = p1.getNameFirstRep().getNameAsSingleString();
Patient p2 = buildPaulPatient();
String p2Name = p2.getNameFirstRep().getNameAsSingleString();
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.MERGE_GOLDEN_RESOURCES));
assertFalse(p2.hasIdElement());
assertFalse(p2.getIdentifier().isEmpty());
assertTrue(p2.getMeta().isEmpty());
assertEquals(2, p2.getName().size());
assertEquals(p2Name, p2.getName().get(0).getNameAsSingleString());
assertEquals(p1Name, p2.getName().get(1).getNameAsSingleString());
assertNull(p2.getBirthDate());
assertEquals(p1.getTelecom().size(), p1.getTelecom().size());
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
}
}

View File

@ -1,59 +1,216 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
import ca.uhn.fhir.mdm.svc.MdmSurvivorshipSvcImpl;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class MdmSurvivorshipSvcImplTest extends BaseMdmR4Test {
@ExtendWith(MockitoExtension.class)
public class MdmSurvivorshipSvcImplTest {
@Autowired
private IMdmSurvivorshipService myMdmSurvivorshipService;
@Spy
private FhirContext myFhirContext = FhirContext.forR4Cached();
@Test
public void testRulesOnCreate() {
Patient p1 = buildFrankPatient();
Patient p2 = new Patient();
@Mock
private DaoRegistry myDaoRegistry;
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.CREATE_RESOURCE));
private GoldenResourceHelper myGoldenResourceHelper;
assertFalse(p2.hasIdElement());
assertTrue(p2.getIdentifier().isEmpty());
assertTrue(p2.getMeta().isEmpty());
// mocks for our GoldenResourceHelper
@Mock
private IMdmSettings myMdmSettings;
@Mock
private EIDHelper myEIDHelper;
@Mock
private MdmPartitionHelper myMdmPartitionHelper;
assertTrue(p1.getNameFirstRep().equalsDeep(p2.getNameFirstRep()));
assertNull(p2.getBirthDate());
assertEquals(p1.getTelecom().size(), p2.getTelecom().size());
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
@Spy
private IIdHelperService<?> myIIdHelperService = new IdHelperService();
@Mock
private IMdmLinkQuerySvc myMdmLinkQuerySvc;
private MdmSurvivorshipSvcImpl mySvc;
@BeforeEach
public void before() {
myGoldenResourceHelper = spy(new GoldenResourceHelper(
myFhirContext,
myMdmSettings,
myEIDHelper,
myMdmPartitionHelper
));
mySvc = new MdmSurvivorshipSvcImpl(
myFhirContext,
myGoldenResourceHelper,
myDaoRegistry,
myMdmLinkQuerySvc,
myIIdHelperService
);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Test
public void testRulesOnMerge() {
Patient p1 = buildFrankPatient();
String p1Name = p1.getNameFirstRep().getNameAsSingleString();
Patient p2 = buildPaulPatient();
String p2Name = p2.getNameFirstRep().getNameAsSingleString();
public void rebuildGoldenResourceCurrentLinksUsingSurvivorshipRules_withManyLinks_rebuildsInUpdateOrder() {
// setup
// create resources
Patient goldenPatient = new Patient();
goldenPatient.addAddress()
.setCity("Toronto")
.addLine("200 fake st");
goldenPatient.addName()
.setFamily("Doe")
.addGiven("Jane");
goldenPatient.setId("Patient/777");
MdmResourceUtil.setMdmManaged(goldenPatient);
MdmResourceUtil.setGoldenResource(goldenPatient);
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(p1, p2, new MdmTransactionContext(MdmTransactionContext.OperationType.MERGE_GOLDEN_RESOURCES));
List<IBaseResource> resources = new ArrayList<>();
List<MdmLinkJson> links = new ArrayList<>();
List<MdmLinkWithRevisionJson> linksWithRevisions = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// we want our resources to be slightly different
Patient patient = new Patient();
patient.addName()
.setFamily("Doe")
.addGiven("John" + i);
patient.addAddress()
.setCity("Toronto")
.addLine(String.format("11%d fake st", i));
patient.addIdentifier()
.setSystem("http://example.com")
.setValue("Value" + i);
patient.setId("Patient/" + i);
resources.add(patient);
assertFalse(p2.hasIdElement());
assertFalse(p2.getIdentifier().isEmpty());
assertTrue(p2.getMeta().isEmpty());
assertEquals(2, p2.getName().size());
assertEquals(p2Name, p2.getName().get(0).getNameAsSingleString());
assertEquals(p1Name, p2.getName().get(1).getNameAsSingleString());
assertNull(p2.getBirthDate());
assertEquals(p1.getTelecom().size(), p1.getTelecom().size());
assertTrue(p2.getTelecomFirstRep().equalsDeep(p1.getTelecomFirstRep()));
MdmLinkJson link = createLinkJson(
patient,
goldenPatient
);
links.add(link);
linksWithRevisions.add(new MdmLinkWithRevisionJson(
link,
1L,
Date.from(
Instant.now().minus(i, ChronoUnit.HOURS)
)
));
}
IFhirResourceDao resourceDao = mock(IFhirResourceDao.class);
// when
when(myDaoRegistry.getResourceDao(eq("Patient")))
.thenReturn(resourceDao);
AtomicInteger counter = new AtomicInteger();
when(resourceDao.readByPid(any()))
.thenAnswer(params -> resources.get(counter.getAndIncrement()));
Page<MdmLinkJson> linkPage = mock(Page.class);
when(myMdmLinkQuerySvc.queryLinks(any(), any()))
.thenReturn(linkPage);
when(linkPage.get())
.thenReturn(links.stream());
when(myMdmSettings.getMdmRules())
.thenReturn(new MdmRulesJson());
// we will return a non-empty list to reduce mocking
when(myEIDHelper.getExternalEid(any()))
.thenReturn(Collections.singletonList(new CanonicalEID("example", "value", "use")));
// test
Patient goldenPatientRebuilt = mySvc.rebuildGoldenResourceWithSurvivorshipRules(
goldenPatient,
createTransactionContext()
);
// verify
assertNotNull(goldenPatientRebuilt);
// make sure it doesn't match the previous golden resource
assertNotEquals(goldenPatient, goldenPatientRebuilt);
assertNotEquals(goldenPatient.getName().get(0).getGiven(), goldenPatientRebuilt.getName().get(0).getGiven());
assertNotEquals(goldenPatient.getAddress().get(0).getLine().get(0), goldenPatientRebuilt.getAddress().get(0).getLine().get(0));
// make sure it's still a golden resource
assertTrue(MdmResourceUtil.isGoldenRecord(goldenPatientRebuilt));
verify(resourceDao)
.update(eq(goldenPatientRebuilt), any(RequestDetails.class));
}
private MdmLink createLinkWithoutUpdateDate(Patient theSource, Patient theGoldenResource) {
MdmLink link = new MdmLink();
link.setCreated(Date.from(
Instant.now().minus(2, ChronoUnit.DAYS)
));
link.setLinkSource(MdmLinkSourceEnum.AUTO);
link.setMatchResult(MdmMatchResultEnum.MATCH);
link.setSourcePersistenceId(JpaPid.fromId(theSource.getIdElement().getIdPartAsLong()));
link.setGoldenResourcePersistenceId(JpaPid.fromId(theGoldenResource.getIdElement().getIdPartAsLong()));
return link;
}
private MdmTransactionContext createTransactionContext() {
MdmTransactionContext context = new MdmTransactionContext();
context.setRestOperation(MdmTransactionContext.OperationType.UPDATE_LINK);
context.setResourceType("Patient");
return context;
}
private MdmLinkJson createLinkJson(
IBaseResource theSource,
IBaseResource theGolden
) {
MdmLinkJson linkJson = new MdmLinkJson();
linkJson.setLinkSource(MdmLinkSourceEnum.AUTO);
linkJson.setGoldenResourceId(theGolden.getIdElement().getValueAsString());
linkJson.setSourceId(theSource.getIdElement().getValueAsString());
linkJson.setMatchResult(MdmMatchResultEnum.MATCH);
return linkJson;
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,6 +20,8 @@
package ca.uhn.fhir.mdm.api;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmCreateOrUpdateParams;
import ca.uhn.fhir.mdm.model.MdmMergeGoldenResourcesParams;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;

View File

@ -20,6 +20,8 @@
package ca.uhn.fhir.mdm.api;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;

View File

@ -28,6 +28,10 @@ import org.hl7.fhir.instance.model.api.IBase;
public interface IMdmSurvivorshipService {
/**
* Merges two golden resources by overwriting all field values on theGoldenResource param for CREATE_RESOURCE,
* UPDATE_RESOURCE, SUBMIT_RESOURCE_TO_MDM, UPDATE_LINK (when setting to MATCH) and MANUAL_MERGE_GOLDEN_RESOURCES.
* PID, identifiers and meta values are not affected by this operation.
*
* Applies survivorship rules to merge fields from the specified target resource to the golden resource. Survivorship
* rules may include, but not limited to the following data consolidation methods:
*
@ -60,4 +64,19 @@ public interface IMdmSurvivorshipService {
*/
<T extends IBase> void applySurvivorshipRulesToGoldenResource(
T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext);
/**
* GoldenResources can have non-empty field data created from changes to the various
* resources that are matched to it (using some pre-defined survivorship rules).
*
* If a match link between a source and golden resource is broken, this method
* will rebuild/repopulate the GoldenResource based on the current links
* and current survivorship rules.
*
* @param theGoldenResource - the golden resource to rebuild
* @param theMdmTransactionContext - the transaction context
* @param <T> - Resource type to apply the survivorship rules to
*/
<T extends IBase> T rebuildGoldenResourceWithSurvivorshipRules(
T theGoldenResource, MdmTransactionContext theMdmTransactionContext);
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.mdm.api;
package ca.uhn.fhir.mdm.api.params;
/*-
* #%L
@ -33,9 +33,24 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class MdmHistorySearchParameters {
public enum JoinType {
AND,
OR
}
private List<IIdType> myGoldenResourceIds = new ArrayList<>();
private List<IIdType> mySourceIds = new ArrayList<>();
/**
* When both myGoldenResourceIds and mySourceIds are provided,
* this parameter determines whether to 'and' or 'or' the query
* that fetches links by these values.
*
* Default (legacy) functionality is 'or'.
*/
private JoinType myParameterJoinType = JoinType.OR;
public MdmHistorySearchParameters() {}
public List<IIdType> getGoldenResourceIds() {
@ -46,6 +61,10 @@ public class MdmHistorySearchParameters {
return mySourceIds;
}
public JoinType getParameterJoinType() {
return myParameterJoinType;
}
public MdmHistorySearchParameters setGoldenResourceIds(List<String> theGoldenResourceIds) {
myGoldenResourceIds = extractId(theGoldenResourceIds);
return this;
@ -56,6 +75,11 @@ public class MdmHistorySearchParameters {
return this;
}
public MdmHistorySearchParameters setJoinType(JoinType theJoinType) {
myParameterJoinType = theJoinType;
return this;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;

View File

@ -17,9 +17,11 @@
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.mdm.api;
package ca.uhn.fhir.mdm.api.params;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
import ca.uhn.fhir.rest.api.SortOrderEnum;

View File

@ -21,12 +21,12 @@ package ca.uhn.fhir.mdm.dao;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmPidTuple;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.model.mdmevents.MdmHistoryEvent;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;
import ca.uhn.fhir.rest.annotation.Operation;

View File

@ -28,8 +28,8 @@ import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmCreateOrUpdateParams;
import ca.uhn.fhir.mdm.model.MdmMergeGoldenResourcesParams;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;

View File

@ -0,0 +1,148 @@
/*-
* #%L
* HAPI FHIR JPA Server - Master Data Management
* %%
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
* %%
* 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%
*/
package ca.uhn.fhir.mdm.svc;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.util.TerserUtil;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.data.domain.Page;
import java.util.stream.Stream;
public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
protected final FhirContext myFhirContext;
private final GoldenResourceHelper myGoldenResourceHelper;
private final DaoRegistry myDaoRegistry;
private final IMdmLinkQuerySvc myMdmLinkQuerySvc;
private final IIdHelperService<?> myIIdHelperService;
public MdmSurvivorshipSvcImpl(
FhirContext theFhirContext,
GoldenResourceHelper theResourceHelper,
DaoRegistry theDaoRegistry,
IMdmLinkQuerySvc theLinkQuerySvc,
IIdHelperService<?> theIIdHelperService) {
myFhirContext = theFhirContext;
myGoldenResourceHelper = theResourceHelper;
myDaoRegistry = theDaoRegistry;
myMdmLinkQuerySvc = theLinkQuerySvc;
myIIdHelperService = theIIdHelperService;
}
// this logic is custom in smile vs hapi
@Override
public <T extends IBase> void applySurvivorshipRulesToGoldenResource(
T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
switch (theMdmTransactionContext.getRestOperation()) {
case MERGE_GOLDEN_RESOURCES:
TerserUtil.mergeFields(
myFhirContext,
(IBaseResource) theTargetResource,
(IBaseResource) theGoldenResource,
TerserUtil.EXCLUDE_IDS_AND_META);
break;
default:
TerserUtil.replaceFields(
myFhirContext,
(IBaseResource) theTargetResource,
(IBaseResource) theGoldenResource,
TerserUtil.EXCLUDE_IDS_AND_META);
break;
}
}
// This logic is the same for all implementations (including jpa or mongo)
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <T extends IBase> T rebuildGoldenResourceWithSurvivorshipRules(
T theGoldenResourceBase, MdmTransactionContext theMdmTransactionContext) {
IBaseResource goldenResource = (IBaseResource) theGoldenResourceBase;
// we want a list of source ids linked to this
// golden resource id; sorted and filtered for only MATCH results
Stream<IBaseResource> sourceResources =
getMatchedSourceIdsByLinkUpdateDate(goldenResource, theMdmTransactionContext);
IBaseResource toSave = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
(IAnyResource) goldenResource,
theMdmTransactionContext,
null // we don't want to apply survivorship - just create a new GoldenResource
);
toSave.setId(goldenResource.getIdElement().toUnqualifiedVersionless());
sourceResources.forEach(source -> {
applySurvivorshipRulesToGoldenResource(source, toSave, theMdmTransactionContext);
});
// save it
IFhirResourceDao dao = myDaoRegistry.getResourceDao(goldenResource.fhirType());
dao.update(toSave, new SystemRequestDetails());
return (T) toSave;
}
private Stream<IBaseResource> getMatchedSourceIdsByLinkUpdateDate(
IBaseResource theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
String resourceType = theGoldenResource.fhirType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
MdmQuerySearchParameters searchParameters = new MdmQuerySearchParameters(new MdmPageRequest(0, 50, 50, 50));
searchParameters.setGoldenResourceId(theGoldenResource.getIdElement());
searchParameters.setSort("myUpdated");
searchParameters.setMatchResult(MdmMatchResultEnum.MATCH);
Page<MdmLinkJson> linksQuery = myMdmLinkQuerySvc.queryLinks(searchParameters, theMdmTransactionContext);
return linksQuery.get().map(link -> {
String sourceId = link.getSourceId();
// +1 because of "/" in id: "ResourceType/Id"
IResourcePersistentId<?> pid = getResourcePID(sourceId.substring(resourceType.length() + 1), resourceType);
// this might be a bit unperformant
// but it depends how many links there are
// per golden resource (unlikely to be thousands)
return dao.readByPid(pid);
});
}
private IResourcePersistentId<?> getResourcePID(String theId, String theResourceType) {
return myIIdHelperService.newPidFromStringIdAndResourceName(theId, theResourceType);
}
}

View File

@ -34,7 +34,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
@Service
public final class EIDHelper {
public class EIDHelper {
private final FhirContext myFhirContext;
private final IMdmSettings myMdmSettings;

View File

@ -58,23 +58,24 @@ public class GoldenResourceHelper {
static final String FIELD_NAME_IDENTIFIER = "identifier";
@Autowired
private IMdmSettings myMdmSettings;
private final IMdmSettings myMdmSettings;
@Autowired
private EIDHelper myEIDHelper;
private final EIDHelper myEIDHelper;
@Autowired
private IMdmSurvivorshipService myMdmSurvivorshipService;
@Autowired
private MdmPartitionHelper myMdmPartitionHelper;
private final MdmPartitionHelper myMdmPartitionHelper;
private final FhirContext myFhirContext;
@Autowired
public GoldenResourceHelper(FhirContext theFhirContext) {
public GoldenResourceHelper(
FhirContext theFhirContext,
IMdmSettings theMdmSettings,
EIDHelper theEIDHelper,
MdmPartitionHelper theMdmPartitionHelper) {
myFhirContext = theFhirContext;
myMdmSettings = theMdmSettings;
myEIDHelper = theEIDHelper;
myMdmPartitionHelper = theMdmPartitionHelper;
}
/**
@ -82,20 +83,28 @@ public class GoldenResourceHelper {
* a randomly generated UUID EID will be created.
*
* @param <T> Supported MDM resource type (e.g. Patient, Practitioner)
* @param theIncomingResource The resource that will be used as the starting point for the MDM linking.
* @param theMdmTransactionContext
* @param theIncomingResource The resource to build the golden resource off of.
* Could be the source resource or another golden resource.
* If a golden resource, do not provide an IMdmSurvivorshipService
* @param theMdmTransactionContext The mdm transaction context
* @param theMdmSurvivorshipService IMdmSurvivorshipSvc. Provide only if survivorshipskills are desired
* to be applied. Provide null otherwise.
*/
@Nonnull
public <T extends IAnyResource> T createGoldenResourceFromMdmSourceResource(
T theIncomingResource, MdmTransactionContext theMdmTransactionContext) {
T theIncomingResource,
MdmTransactionContext theMdmTransactionContext,
IMdmSurvivorshipService theMdmSurvivorshipService) {
validateContextSupported();
// get a ref to the actual ID Field
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(theIncomingResource);
IBaseResource newGoldenResource = resourceDefinition.newInstance();
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
if (theMdmSurvivorshipService != null) {
theMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
theIncomingResource, newGoldenResource, theMdmTransactionContext);
}
// hapi has 2 metamodels: for children and types
BaseRuntimeChildDefinition goldenResourceIdentifier = resourceDefinition.getChildByName(FIELD_NAME_IDENTIFIER);
@ -322,14 +331,6 @@ public class GoldenResourceHelper {
myFhirContext, theFromGoldenResource, theToGoldenResource, FIELD_NAME_IDENTIFIER);
}
public void mergeNonIdentiferFields(
IBaseResource theFromGoldenResource,
IBaseResource theToGoldenResource,
MdmTransactionContext theMdmTransactionContext) {
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
}
/**
* An incoming resource is a potential duplicate if it matches a source that has a golden resource with an official
* EID, but the incoming resource also has an EID that does not match.

View File

@ -118,6 +118,12 @@ public final class MdmResourceUtil {
MdmConstants.DISPLAY_GOLDEN_RECORD);
}
/**
* Sets the provided resource as 'redirected' golden resource.
* This is done when a Golden Resource has been deprecated
* and is no longer the primary golden resource (for example,
* after a merge of 2 golden resources).
*/
public static IBaseResource setGoldenResourceRedirected(IBaseResource theBaseResource) {
return setTagOnResource(
theBaseResource,

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.mdm.api;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import org.junit.jupiter.api.Test;
import java.util.Collections;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -21,7 +21,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.9.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

Some files were not shown because too many files have changed in this diff Show More