Resovle 5505-remove-broken-member-match-from-hapi (#5517)

* - Removed IConsentExtensionProvider, MemberMatchR4ResourceProvider and MemberMatcherR4Helper, as well as corresponding usages and tests

* - Removed IMemberMatchConsentHook.java

* spotless reformat

* added changelog

* removed constants for $member-match parameters

* removed error message for $member-match

* completely removed MemberMatchR4ResourceProvider.java

* removed operation constant for $member-match

* version bump
This commit is contained in:
TynerGjs 2023-12-07 13:16:31 -05:00 committed by GitHub
parent 4d0670b0ad
commit d8298e38d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 89 additions and 1658 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -220,21 +220,6 @@ public class Constants {
public static final String PARAM_TEXT = "_text";
public static final String PARAM_VALIDATE = "_validate";
/**
* $member-match operation
*/
public static final String PARAM_MEMBER_PATIENT = "MemberPatient";
public static final String PARAM_MEMBER_IDENTIFIER = "MemberIdentifier";
public static final String COVERAGE_TO_MATCH = "CoverageToMatch";
public static final String COVERAGE_TO_LINK = "CoverageToLink";
public static final String PARAM_CONSENT = "Consent";
public static final String PARAM_MEMBER_PATIENT_NAME = PARAM_MEMBER_PATIENT + " Name";
public static final String PARAM_MEMBER_PATIENT_BIRTHDATE = PARAM_MEMBER_PATIENT + " Birthdate";
public static final String PARAM_CONSENT_PATIENT_REFERENCE = PARAM_CONSENT + "'s Patient Reference";
public static final String PARAM_CONSENT_PERFORMER_REFERENCE = PARAM_CONSENT + "'s Performer Reference";
public static final String PARAMQUALIFIER_MISSING = ":missing";
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
public static final String PARAMQUALIFIER_MISSING_TRUE = "true";

View File

@ -206,10 +206,3 @@ ca.uhn.fhir.jpa.provider.DiffProvider.cantDiffDifferentTypes=Unable to diff two
ca.uhn.fhir.jpa.interceptor.validation.RuleRequireProfileDeclaration.noMatchingProfile=Resource of type "{0}" does not declare conformance to profile from: {1}
ca.uhn.fhir.jpa.interceptor.validation.RuleRequireProfileDeclaration.illegalProfile=Resource of type "{0}" must not declare conformance to profile: {1}
operation.member.match.error.coverage.not.found=Could not find coverage for member based on coverage id or coverage identifier.
operation.member.match.error.beneficiary.not.found=Could not find beneficiary for coverage.
operation.member.match.error.missing.parameter=Parameter "{0}" is required.
operation.member.match.error.beneficiary.without.identifier=Coverage beneficiary does not have an identifier.
operation.member.match.error.patient.not.found=Could not find matching patient for coverage.
operation.member.match.error.consent.release.data.mismatch=Consent policy does not match the data release segmentation capabilities.

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,4 @@
---
type: remove
issue: 5505
title: "Removed an incorrect implementation of $member-match."

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4;
import ca.uhn.fhir.jpa.config.JpaConfig;
@ -31,9 +30,6 @@ import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.jpa.provider.r4.IMemberMatchConsentHook;
import ca.uhn.fhir.jpa.provider.r4.MemberMatchR4ResourceProvider;
import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR4;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
@ -42,12 +38,8 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -104,21 +96,4 @@ public class JpaR4Config {
ITermDeferredStorageSvc theDeferredStorageSvc, ITermCodeSystemStorageSvc theCodeSystemStorageSvc) {
return new TermLoaderSvcImpl(theDeferredStorageSvc, theCodeSystemStorageSvc);
}
@Bean
public MemberMatcherR4Helper memberMatcherR4Helper(
@Autowired FhirContext theContext,
@Autowired IFhirResourceDao<Coverage> theCoverageDao,
@Autowired IFhirResourceDao<Patient> thePatientDao,
@Autowired IFhirResourceDao<Consent> theConsentDao,
@Autowired(required = false) IMemberMatchConsentHook theExtensionProvider) {
return new MemberMatcherR4Helper(
theContext, theCoverageDao, thePatientDao, theConsentDao, theExtensionProvider);
}
@Bean
public MemberMatchR4ResourceProvider memberMatchR4ResourceProvider(
FhirContext theFhirContext, MemberMatcherR4Helper theMemberMatchR4Helper) {
return new MemberMatchR4ResourceProvider(theFhirContext, theMemberMatchR4Helper);
}
}

View File

@ -1,51 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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.provider.r4;
import ca.uhn.fhir.util.ExtensionUtil;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
/**
* Hook for Consent pre-save additions.
*
* @deprecated - we just use Consumer now
* TODO delete this.
*/
@Deprecated(since = "6.3.6", forRemoval = true)
public interface IConsentExtensionProvider extends IMemberMatchConsentHook {
Logger ourLog = LoggerFactory.getLogger(IConsentExtensionProvider.class);
Collection<IBaseExtension> getConsentExtension(IBaseResource theConsentResource);
default void accept(IBaseResource theResource) {
Collection<IBaseExtension> extensions = getConsentExtension(theResource);
for (IBaseExtension ext : extensions) {
IBaseExtension<?, ?> e = ExtensionUtil.addExtension(theResource, ext.getUrl());
e.setValue(ext.getValue());
}
ourLog.trace("{} extension(s) added to Consent", extensions.size());
}
}

View File

@ -1,29 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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.provider.r4;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.function.Consumer;
/**
* Pre-save hook for Consent saved during $member-match.
*/
public interface IMemberMatchConsentHook extends Consumer<IBaseResource> {}

View File

@ -1,171 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import jakarta.annotation.Nullable;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import java.util.Optional;
public class MemberMatchR4ResourceProvider {
private final MemberMatcherR4Helper myMemberMatcherR4Helper;
private final FhirContext myFhirContext;
public MemberMatchR4ResourceProvider(FhirContext theFhirContext, MemberMatcherR4Helper theMemberMatcherR4Helper) {
myFhirContext = theFhirContext;
myMemberMatcherR4Helper = theMemberMatcherR4Helper;
}
/**
* /Patient/$member-match operation
* Basic implementation matching by coverage id or by coverage identifier. Matching by
* Beneficiary (Patient) demographics on family name and birthdate in this version
*/
@Operation(
name = ProviderConstants.OPERATION_MEMBER_MATCH,
typeName = "Patient",
canonicalUrl = "http://hl7.org/fhir/us/davinci-hrex/OperationDefinition/member-match",
idempotent = false,
returnParameters = {@OperationParam(name = "MemberIdentifier", typeName = "string")})
public Parameters patientMemberMatch(
jakarta.servlet.http.HttpServletRequest theServletRequest,
@Description(shortDefinition = "The target of the operation. Contain member Patient demographics.")
@OperationParam(name = Constants.PARAM_MEMBER_PATIENT, min = 1, max = 1)
Patient theMemberPatient,
@Description(
shortDefinition =
"Old coverage information as extracted from beneficiary's card. Identifies the coverage to be matched by the receiving payer.")
@OperationParam(name = Constants.COVERAGE_TO_MATCH, min = 1, max = 1)
Coverage oldCoverage,
@Description(
shortDefinition =
"New Coverage information. Identifies the coverage information of the member as they are known by the requesting payer. Provided as a reference.")
@OperationParam(name = Constants.COVERAGE_TO_LINK, min = 1, max = 1)
Coverage newCoverage,
@Description(
shortDefinition =
"Consent information. Consent held by the system seeking the match that grants permission to access the patient information.")
@OperationParam(name = Constants.PARAM_CONSENT, min = 1, max = 1)
Consent theConsent,
RequestDetails theRequestDetails) {
return doMemberMatchOperation(theMemberPatient, oldCoverage, newCoverage, theConsent, theRequestDetails);
}
private Parameters doMemberMatchOperation(
Patient theMemberPatient,
Coverage theCoverageToMatch,
Coverage theCoverageToLink,
Consent theConsent,
RequestDetails theRequestDetails) {
validateParams(theMemberPatient, theCoverageToMatch, theCoverageToLink, theConsent);
Optional<Coverage> coverageOpt =
myMemberMatcherR4Helper.findMatchingCoverage(theCoverageToMatch, theRequestDetails);
if (coverageOpt.isEmpty()) {
String i18nMessage =
myFhirContext.getLocalizer().getMessage("operation.member.match.error.coverage.not.found");
throw new UnprocessableEntityException(Msg.code(1155) + i18nMessage);
}
Coverage coverage = coverageOpt.get();
Optional<Patient> patientOpt = myMemberMatcherR4Helper.getBeneficiaryPatient(coverage, theRequestDetails);
if (patientOpt.isEmpty()) {
String i18nMessage =
myFhirContext.getLocalizer().getMessage("operation.member.match.error.beneficiary.not.found");
throw new UnprocessableEntityException(Msg.code(1156) + i18nMessage);
}
Patient patient = patientOpt.get();
if (!myMemberMatcherR4Helper.validPatientMember(patient, theMemberPatient, theRequestDetails)) {
String i18nMessage =
myFhirContext.getLocalizer().getMessage("operation.member.match.error.patient.not.found");
throw new UnprocessableEntityException(Msg.code(2146) + i18nMessage);
}
if (patient.getIdentifier().isEmpty()) {
String i18nMessage = myFhirContext
.getLocalizer()
.getMessage("operation.member.match.error.beneficiary.without.identifier");
throw new UnprocessableEntityException(Msg.code(1157) + i18nMessage);
}
if (!myMemberMatcherR4Helper.validConsentDataAccess(theConsent)) {
String i18nMessage = myFhirContext
.getLocalizer()
.getMessage("operation.member.match.error.consent.release.data.mismatch");
throw new UnprocessableEntityException(Msg.code(2147) + i18nMessage);
}
myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep());
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient, theRequestDetails);
return myMemberMatcherR4Helper.buildSuccessReturnParameters(patient);
}
private void validateParams(
Patient theMemberPatient, Coverage theOldCoverage, Coverage theNewCoverage, Consent theConsent) {
validateParam(theMemberPatient, Constants.PARAM_MEMBER_PATIENT);
validateParam(theOldCoverage, Constants.COVERAGE_TO_MATCH);
validateParam(theNewCoverage, Constants.COVERAGE_TO_LINK);
validateParam(theConsent, Constants.PARAM_CONSENT);
validateMemberPatientParam(theMemberPatient);
validateConsentParam(theConsent);
}
private void validateParam(@Nullable Object theParam, String theParamName) {
if (theParam == null) {
String i18nMessage = myFhirContext
.getLocalizer()
.getMessage("operation.member.match.error.missing.parameter", theParamName);
throw new UnprocessableEntityException(Msg.code(1158) + i18nMessage);
}
}
private void validateMemberPatientParam(Patient theMemberPatient) {
if (theMemberPatient.getName().isEmpty()) {
validateParam(null, Constants.PARAM_MEMBER_PATIENT_NAME);
}
validateParam(theMemberPatient.getName().get(0).getFamily(), Constants.PARAM_MEMBER_PATIENT_NAME);
validateParam(theMemberPatient.getBirthDate(), Constants.PARAM_MEMBER_PATIENT_BIRTHDATE);
}
private void validateConsentParam(Consent theConsent) {
if (theConsent.getPatient().isEmpty()) {
validateParam(null, Constants.PARAM_CONSENT_PATIENT_REFERENCE);
}
if (theConsent.getPerformer().isEmpty()) {
validateParam(null, Constants.PARAM_CONSENT_PERFORMER_REFERENCE);
}
}
}

View File

@ -1,285 +0,0 @@
/*
* #%L
* HAPI FHIR JPA Server
* %%
* 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.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
public class MemberMatcherR4Helper {
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MemberMatcherR4Helper.class);
private static final String OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/v2-0203";
private static final String OUT_COVERAGE_IDENTIFIER_CODE = "MB";
private static final String OUT_COVERAGE_IDENTIFIER_TEXT = "Member Number";
private static final String COVERAGE_TYPE = "Coverage";
private static final String CONSENT_POLICY_REGULAR_TYPE = "regular";
private static final String CONSENT_POLICY_SENSITIVE_TYPE = "sensitive";
public static final String CONSENT_IDENTIFIER_CODE_SYSTEM =
"https://smilecdr.com/fhir/ns/member-match-source-client";
private final FhirContext myFhirContext;
private final IFhirResourceDao<Coverage> myCoverageDao;
private final IFhirResourceDao<Patient> myPatientDao;
private final IFhirResourceDao<Consent> myConsentDao;
/** A hook to modify the Consent before save */
private final Consumer<IBaseResource> myConsentModifier;
private boolean myRegularFilterSupported = false;
public MemberMatcherR4Helper(
FhirContext theContext,
IFhirResourceDao<Coverage> theCoverageDao,
IFhirResourceDao<Patient> thePatientDao,
IFhirResourceDao<Consent> theConsentDao,
@Nullable IMemberMatchConsentHook theConsentModifier) {
myFhirContext = theContext;
myConsentDao = theConsentDao;
myPatientDao = thePatientDao;
myCoverageDao = theCoverageDao;
myConsentModifier = (theConsentModifier != null) ? theConsentModifier : noop -> {};
}
/**
* Find Coverage matching the received member (Patient) by coverage id or by coverage identifier only
*/
public Optional<Coverage> findMatchingCoverage(Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
// search by received old coverage id
List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch, theRequestDetails);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of((Coverage) foundCoverages.get(0));
}
// search by received old coverage identifier
foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch, theRequestDetails);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of((Coverage) foundCoverages.get(0));
}
return Optional.empty();
}
private List<IBaseResource> findCoverageByCoverageIdentifier(
Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
TokenOrListParam identifierParam = new TokenOrListParam();
for (Identifier identifier : theCoverageToMatch.getIdentifier()) {
identifierParam.add(identifier.getSystem(), identifier.getValue());
}
SearchParameterMap paramMap = new SearchParameterMap().add("identifier", identifierParam);
ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap, theRequestDetails);
return retVal.getAllResources();
}
private boolean isCoverage(IBaseResource theIBaseResource) {
return theIBaseResource.fhirType().equals(COVERAGE_TYPE);
}
private List<IBaseResource> findCoverageByCoverageId(
Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
SearchParameterMap paramMap = new SearchParameterMap().add("_id", new StringParam(theCoverageToMatch.getId()));
ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap, theRequestDetails);
return retVal.getAllResources();
}
public void updateConsentForMemberMatch(
Consent theConsent, Patient thePatient, Patient theMemberPatient, RequestDetails theRequestDetails) {
updateConsentPatientAndPerformer(theConsent, thePatient);
myConsentModifier.accept(theConsent);
// Trust RequestTenantPartitionInterceptor or PatientIdPartitionInterceptor to assign the partition.
myConsentDao.create(theConsent, theRequestDetails);
}
public Parameters buildSuccessReturnParameters(Patient thePatient) {
IBaseParameters parameters = ParametersUtil.newInstance(myFhirContext);
ParametersUtil.addParameterToParameters(
myFhirContext,
parameters,
PARAM_MEMBER_IDENTIFIER,
thePatient.getIdElement().toUnqualifiedVersionless());
return (Parameters) parameters;
}
private Identifier getIdentifier(Patient theMemberPatient) {
return theMemberPatient.getIdentifier().stream()
.filter(this::isTypeMB)
.findFirst()
.orElseThrow(() -> {
String i18nMessage = myFhirContext
.getLocalizer()
.getMessage("operation.member.match.error.beneficiary.without.identifier");
return new UnprocessableEntityException(Msg.code(2219) + i18nMessage);
});
}
private boolean isTypeMB(Identifier theMemberIdentifier) {
return theMemberIdentifier.getType() != null
&& theMemberIdentifier.getType().getCoding().stream()
.anyMatch(typeCoding -> typeCoding.getCode().equals("MB"));
}
public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) {
Coding coding = new Coding()
.setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM)
.setCode(OUT_COVERAGE_IDENTIFIER_CODE)
.setDisplay(OUT_COVERAGE_IDENTIFIER_TEXT)
.setUserSelected(false);
CodeableConcept concept =
new CodeableConcept().setCoding(Lists.newArrayList(coding)).setText(OUT_COVERAGE_IDENTIFIER_TEXT);
Identifier newIdentifier = new Identifier()
.setUse(Identifier.IdentifierUse.USUAL)
.setType(concept)
.setSystem(theNewIdentifier.getSystem())
.setValue(theNewIdentifier.getValue());
theMemberPatient.addIdentifier(newIdentifier);
}
public Optional<Patient> getBeneficiaryPatient(Coverage theCoverage, RequestDetails theRequestDetails) {
if (theCoverage.getBeneficiaryTarget() == null && theCoverage.getBeneficiary() == null) {
return Optional.empty();
}
if (theCoverage.getBeneficiaryTarget() != null
&& !theCoverage.getBeneficiaryTarget().getIdentifier().isEmpty()) {
return Optional.of(theCoverage.getBeneficiaryTarget());
}
Reference beneficiaryRef = theCoverage.getBeneficiary();
if (beneficiaryRef == null) {
return Optional.empty();
}
if (beneficiaryRef.getResource() != null) {
return Optional.of((Patient) beneficiaryRef.getResource());
}
if (beneficiaryRef.getReference() == null) {
return Optional.empty();
}
Patient beneficiary = myPatientDao.read(new IdDt(beneficiaryRef.getReference()), theRequestDetails);
return Optional.ofNullable(beneficiary);
}
/**
* Matching by member patient demographics - family name and birthdate only
*/
public boolean validPatientMember(
Patient thePatientFromContract, Patient thePatientToMatch, RequestDetails theRequestDetails) {
if (thePatientFromContract == null
|| thePatientFromContract.getIdElement() == null
|| thePatientToMatch == null) {
return false;
}
StringOrListParam familyName = new StringOrListParam();
for (HumanName name : thePatientToMatch.getName()) {
familyName.addOr(new StringParam(name.getFamily()));
}
SearchParameterMap map = new SearchParameterMap()
.add("family", familyName)
.add(
"birthdate",
new DateParam(thePatientToMatch.getBirthDateElement().getValueAsString()));
ca.uhn.fhir.rest.api.server.IBundleProvider bundle = myPatientDao.search(map, theRequestDetails);
for (IBaseResource patientResource : bundle.getAllResources()) {
IIdType patientId = patientResource.getIdElement().toUnqualifiedVersionless();
if (patientId
.getValue()
.equals(thePatientFromContract
.getIdElement()
.toUnqualifiedVersionless()
.getValue())) {
return true;
}
}
return false;
}
public boolean validConsentDataAccess(Consent theConsent) {
if (theConsent.getPolicy().isEmpty()) {
return false;
}
for (Consent.ConsentPolicyComponent policyComponent : theConsent.getPolicy()) {
if (policyComponent.getUri() == null || !validConsentPolicy(policyComponent.getUri())) {
return false;
}
}
return true;
}
/**
* The consent policy rules are
* <a href="https://build.fhir.org/ig/HL7/davinci-ehrx/StructureDefinition-hrex-consent.html#notes">
* described here.</a>
*/
private boolean validConsentPolicy(String thePolicyUri) {
String policyTypes = StringUtils.substringAfterLast(thePolicyUri, "#");
if (policyTypes.equals(CONSENT_POLICY_SENSITIVE_TYPE)) {
return true;
}
return policyTypes.equals(CONSENT_POLICY_REGULAR_TYPE) && myRegularFilterSupported;
}
public void setRegularFilterSupported(boolean theRegularFilterSupported) {
myRegularFilterSupported = theRegularFilterSupported;
}
private void updateConsentPatientAndPerformer(Consent theConsent, Patient thePatient) {
String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue();
theConsent.getPatient().setReference(patientRef);
theConsent.getPerformer().set(0, new Reference(patientRef));
}
}

View File

@ -1,544 +0,0 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.test.util.LogbackCaptureTestExtension;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.google.common.collect.Lists;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class MemberMatcherR4HelperTest {
@RegisterExtension
LogbackCaptureTestExtension myLogCapture = new LogbackCaptureTestExtension((Logger) MemberMatcherR4Helper.ourLog, Level.TRACE);
@Spy
private final FhirContext myFhirContext = FhirContext.forR4Cached();
@Mock
private IFhirResourceDao<Coverage> myCoverageDao;
@Mock
private IFhirResourceDao<Patient> myPatientDao;
@Mock
private IFhirResourceDao<Consent> myConsentDao;
private MemberMatcherR4Helper myHelper;
RequestDetails myRequestDetails = new SystemRequestDetails();
@BeforeEach
public void before() {
myHelper = new MemberMatcherR4Helper(
myFhirContext,
myCoverageDao,
myPatientDao,
myConsentDao,
null // extension provider
);
}
@Mock private Coverage myCoverageToMatch;
@Mock private IBundleProvider myBundleProvider;
private final Coverage myMatchedCoverage = new Coverage()
.setBeneficiary(new Reference("Patient/123"));
private final Identifier myMatchingIdentifier = new Identifier()
.setSystem("identifier-system").setValue("identifier-value");
@Captor
ArgumentCaptor<SearchParameterMap> mySearchParameterMapCaptor;
@Test
void findMatchingCoverageMatchByIdReturnsMatched() {
when(myCoverageToMatch.getId()).thenReturn("cvg-to-match-id");
when(myCoverageDao.search(isA(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn(Collections.singletonList(myMatchedCoverage));
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertEquals(Optional.of(myMatchedCoverage), result);
verify(myCoverageDao).search(mySearchParameterMapCaptor.capture(), same(myRequestDetails));
SearchParameterMap spMap = mySearchParameterMapCaptor.getValue();
assertTrue(spMap.containsKey("_id"));
List<List<IQueryParameterType>> listListParams = spMap.get("_id");
assertEquals(1, listListParams.size());
assertEquals(1, listListParams.get(0).size());
IQueryParameterType param = listListParams.get(0).get(0);
assertEquals("cvg-to-match-id", param.getValueAsQueryToken(myFhirContext));
}
@Test
void findMatchingCoverageMatchByIdentifierReturnsMatched() {
when(myCoverageToMatch.getId()).thenReturn("non-matching-id");
when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier));
when(myCoverageDao.search(isA(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn(
Collections.emptyList(), Collections.singletonList(myMatchedCoverage));
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertEquals(Optional.of(myMatchedCoverage), result);
verify(myCoverageDao, times(2)).search(mySearchParameterMapCaptor.capture(), same(myRequestDetails));
List<SearchParameterMap> spMap = mySearchParameterMapCaptor.getAllValues();
assertTrue(spMap.get(0).containsKey("_id"));
assertTrue(spMap.get(1).containsKey("identifier"));
List<List<IQueryParameterType>> listListParams = spMap.get(1).get("identifier");
assertEquals(1, listListParams.size());
assertEquals(1, listListParams.get(0).size());
IQueryParameterType param = listListParams.get(0).get(0);
assertEquals(myMatchingIdentifier.getSystem() + "|" + myMatchingIdentifier.getValue(),
param.getValueAsQueryToken(myFhirContext));
}
@Test
void findMatchingCoverageNoMatchReturnsEmpty() {
when(myCoverageToMatch.getId()).thenReturn("non-matching-id");
when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier));
when(myCoverageDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn(Collections.emptyList(), Collections.emptyList());
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertFalse(result.isPresent());
}
@Test
void buildSuccessReturnParameters() {
Identifier identifier = new Identifier();
CodeableConcept identifierType = new CodeableConcept();
identifierType.addCoding(new Coding("", "MB", ""));
identifier.setType(identifierType);
Patient patient = new Patient();
patient.setId("Patient/test123");
patient.addIdentifier(identifier);
Parameters result = myHelper.buildSuccessReturnParameters(patient);
assertEquals(PARAM_MEMBER_IDENTIFIER, result.getParameter().get(0).getName());
assertEquals(patient.getId(), ((IdType)(result.getParameter().get(0).getValue())).getValue());
}
@Test
void addMemberIdentifierToMemberPatient() {
Identifier originalIdentifier = new Identifier()
.setSystem("original-identifier-system").setValue("original-identifier-value");
Identifier newIdentifier = new Identifier()
.setSystem("new-identifier-system").setValue("new-identifier-value");
Patient patient = new Patient().setIdentifier(Lists.newArrayList(originalIdentifier));
myHelper.addMemberIdentifierToMemberPatient(patient, newIdentifier);
assertEquals(2, patient.getIdentifier().size());
assertEquals("original-identifier-system", patient.getIdentifier().get(0).getSystem());
assertEquals("original-identifier-value", patient.getIdentifier().get(0).getValue());
assertEquals("new-identifier-system", patient.getIdentifier().get(1).getSystem());
assertEquals("new-identifier-value", patient.getIdentifier().get(1).getValue());
}
/**
* Testing multiple scenarios for getting patient resource from coverage's plan beneficiary
*/
@Nested
public class TestGetBeneficiaryPatient {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Coverage coverage;
@Test
void noBeneficiaryOrBeneficiaryTargetReturnsEmpty() {
when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary()).thenReturn(null);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent());
}
@Test
void beneficiaryTargetWithNoIdentifierReturnsEmpty() {
when(coverage.getBeneficiary()).thenReturn(null);
when(coverage.getBeneficiaryTarget()).thenReturn(new Patient());
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent());
}
@Test
void beneficiaryTargetWithIdentifierReturnsBeneficiary() {
Patient patient = new Patient().setIdentifier(Collections.singletonList(new Identifier()));
when(coverage.getBeneficiaryTarget()).thenReturn(patient);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertTrue(result.isPresent());
assertEquals(patient, result.get());
}
@Test
void beneficiaryReferenceResourceReturnsBeneficiary() {
Patient patient = new Patient().setIdentifier(Collections.singletonList(new Identifier()));
when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary().getResource()).thenReturn(patient);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertTrue(result.isPresent());
assertEquals(patient, result.get());
}
@Test
void beneficiaryReferenceNoResourceOrReferenceReturnsEmpty() {
when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary()).thenReturn(new Reference());
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent());
}
@Test
void beneficiaryReferenceReferenceReturnsReadPatient() {
when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary().getResource()).thenReturn(null);
when(coverage.getBeneficiary().getReference()).thenReturn("patient-id");
myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
verify(myPatientDao).read(new IdDt("patient-id"), myRequestDetails);
}
}
/**
* Testing multiple scenarios for validity of Patient Member parameter
*/
@Nested
public class TestValidPatientMember {
private final Patient patient = new Patient();
@Test
void noPatientFoundFromContractReturnsFalse() {
boolean result = myHelper.validPatientMember(null, patient, myRequestDetails);
assertFalse(result);
}
@Test
void noPatientFoundFromPatientMemberReturnsFalse() {
boolean result = myHelper.validPatientMember(patient, null, myRequestDetails);
assertFalse(result);
}
@Test
void noMatchingFamilyNameReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider;
});
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result);
}
@Test
void noMatchingBirthdayReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider;
});
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result);
}
@Test
void noMatchingFieldsReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider;
});
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result);
}
@Test
void patientMatchingReturnTrue() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(patientFromContractFound));
return provider;
});
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertTrue(result);
}
private Patient getPatientWithNoIDParm(String familyName, String birthdate) {
Patient patient = new Patient().setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily(familyName)))
.setBirthDateElement(new DateType(birthdate));
return patient;
}
private Patient getPatientWithIDParm(String id, String familyName, String birthdate) {
Patient patient = getPatientWithNoIDParm(familyName, birthdate);
patient.setId(id);
return patient;
}
}
/**
* Testing multiple scenarios for consent's policy data that is defined in
* https://build.fhir.org/ig/HL7/davinci-ehrx/StructureDefinition-hrex-consent.html#notes
*/
@Nested
public class TestValidvalidConsentDataAccess {
private Consent consent;
@Test
void noConsentProfileFoundReturnsFalse() {
consent = new Consent();
boolean result = myHelper.validConsentDataAccess(consent);
assertFalse(result);
}
@Test
void noDataAccessValueProvidedReturnsFalse() {
consent = getConsent();
boolean result = myHelper.validConsentDataAccess(consent);
assertFalse(result);
}
@Test
void wrongDataAccessValueProvidedReturnsFalse() {
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#access_data"));
boolean result = myHelper.validConsentDataAccess(consent);
assertFalse(result);
}
@Test
void regularDataAccessWithRegularNotAllowedReturnsFalse() {
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#regular"));
boolean result = myHelper.validConsentDataAccess(consent);
assertFalse(result);
}
@Test
void regularDataAccessWithRegularAllowedReturnsTrue() {
myHelper.setRegularFilterSupported(true);
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#regular"));
boolean result = myHelper.validConsentDataAccess(consent);
assertTrue(result);
}
@Test
void sensitiveDataAccessAllowedReturnsTrue() {
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
boolean result = myHelper.validConsentDataAccess(consent);
assertTrue(result);
}
@Test
void multipleSensitivePolicyDataAccessAllowedReturnsTrue() {
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
boolean result = myHelper.validConsentDataAccess(consent);
assertTrue(result);
}
@Test
void multipleRegularPolicyDataAccessWithRegularAllowedReturnsTrue() {
myHelper.setRegularFilterSupported(true);
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#regular"));
consent.addPolicy(constructConsentPolicyComponent("#regular"));
boolean result = myHelper.validConsentDataAccess(consent);
assertTrue(result);
}
@Test
void multipleMixedPolicyDataAccessWithRegularNotAllowedReturnsFalse() {
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#regular"));
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
boolean result = myHelper.validConsentDataAccess(consent);
assertFalse(result);
}
@Test
void multipleMixedPolicyDataAccessWithRegularAllowedReturnsTrue() {
myHelper.setRegularFilterSupported(true);
consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#regular"));
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
boolean result = myHelper.validConsentDataAccess(consent);
assertTrue(result);
}
}
private Consent getConsent() {
Consent consent = new Consent();
consent.getPerformer().add(new Reference("Patient/1"));
return consent;
}
private Consent.ConsentPolicyComponent constructConsentPolicyComponent(String uriAccess) {
String uri = "http://hl7.org/fhir/us/davinci-hrex/StructureDefinition-hrex-consent.html";
return new Consent.ConsentPolicyComponent().setUri(uri + uriAccess);
}
private Patient createPatientForMemberMatchUpdate(boolean addIdentifier) {
Patient patient = new Patient();
patient.setId("Patient/RED");
Identifier identifier = new Identifier();
if (addIdentifier) {
CodeableConcept identifierType = new CodeableConcept();
identifierType.addCoding(new Coding("", "MB", ""));
identifier.setType(identifierType);
}
identifier.setValue("RED-Patient");
patient.addIdentifier(identifier);
return patient;
}
@Nested
public class MemberMatchWithoutConsentProvider {
@Captor
private ArgumentCaptor<Consent> myDaoCaptor;
@Test
public void updateConsentForMemberMatch_noProvider_UpdatePatientButNotExtensionAndSaves() {
// setup
Consent consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
// test
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient, myRequestDetails);
// verify
verify(myConsentDao).create(myDaoCaptor.capture(), same(myRequestDetails));
Consent saved = myDaoCaptor.getValue();
// check consent patient info
String patientRef = patient.getIdElement().toUnqualifiedVersionless().getValue();
assertEquals(patientRef, saved.getPatient().getReference());
assertEquals(patientRef, saved.getPerformer().get(0).getReference());
assertThat(myLogCapture.getLogEvents(), empty());
}
}
@Nested
public class MemberMatchWithConsentProvider {
@Mock
private IConsentExtensionProvider myExtensionProvider;
@Captor
private ArgumentCaptor<Consent> myHookCaptor;
@BeforeEach
public void before() {
myHelper = new MemberMatcherR4Helper(
myFhirContext,
myCoverageDao,
myPatientDao,
myConsentDao,
myExtensionProvider
);
}
@Test
public void addClientIdAsExtensionToConsentIfAvailable_withProvider_addsExtensionAndSaves() {
// setup
Consent consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
consent.setId("Consent/RED");
Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true);
// test
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient, myRequestDetails);
// verify
verify(myExtensionProvider).accept(myHookCaptor.capture());
assertSame(consent, myHookCaptor.getValue());
assertThat(myLogCapture.getLogEvents(), empty());
}
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -1,440 +0,0 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.model.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static ca.uhn.fhir.rest.api.Constants.*;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings("Duplicates")
public class PatientMemberMatchOperationR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PatientMemberMatchOperationR4Test.class);
private static final String ourQuery = "/Patient/$member-match?_format=json";
private static final String PATIENT_ID = "Patient/A123";
private static final String EXISTING_COVERAGE_ID = "cov-id-123";
private static final String EXISTING_COVERAGE_IDENT_SYSTEM = "http://centene.com/insurancePlanIds";
private static final String EXISTING_COVERAGE_IDENT_VALUE = "U1234567890";
private static final String EXISTING_COVERAGE_PATIENT_IDENT_SYSTEM = "http://oldhealthplan.example.com";
private static final String EXISTING_COVERAGE_PATIENT_IDENT_VALUE = "DHU-55678";
private static final String CONSENT_POLICY_SENSITIVE_DATA_URI = "http://hl7.org/fhir/us/davinci-hrex/StructureDefinition-hrex-consent.html#sensitive";
private static final String CONSENT_POLICY_REGULAR_DATA_URI = "http://hl7.org/fhir/us/davinci-hrex/StructureDefinition-hrex-consent.html#regular";
private Identifier ourExistingCoverageIdentifier;
private Patient myPatient;
private Coverage coverageToMatch; // Old Coverage (must match field)
private Coverage coverageToLink;
private Consent myConsent;
@Autowired
MemberMatcherR4Helper myMemberMatcherR4Helper;
@Autowired
MemberMatchR4ResourceProvider theMemberMatchR4ResourceProvider;
@BeforeEach
public void beforeDisableResultReuse() {
myStorageSettings.setReuseCachedSearchResultsForMillis(null);
}
@Override
@AfterEach
public void after() throws Exception {
super.after();
myStorageSettings.setReuseCachedSearchResultsForMillis(new JpaStorageSettings().getReuseCachedSearchResultsForMillis());
myStorageSettings.setEverythingIncludesFetchPageSize(new JpaStorageSettings().getEverythingIncludesFetchPageSize());
myStorageSettings.setSearchPreFetchThresholds(new JpaStorageSettings().getSearchPreFetchThresholds());
myStorageSettings.setAllowExternalReferences(new JpaStorageSettings().isAllowExternalReferences());
myServer.getRestfulServer().unregisterProvider(theMemberMatchR4ResourceProvider);
}
@Override
@BeforeEach
public void before() throws Exception {
super.before();
myFhirContext.setParserErrorHandler(new StrictErrorHandler());
myPatient = (Patient) new Patient().setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily("Person")))
.setBirthDateElement(new DateType("2020-01-01"))
.setId(PATIENT_ID);
// Old Coverage (must match field)
coverageToMatch = (Coverage) new Coverage()
.setId(EXISTING_COVERAGE_ID);
// New Coverage (must return unchanged)
coverageToLink = (Coverage) new Coverage()
.setIdentifier(Lists.newArrayList(new Identifier().setSystem("http://newealthplan.example.com").setValue("234567")))
.setId("AA87654");
myConsent = new Consent()
.setStatus(Consent.ConsentState.ACTIVE)
.setScope(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/consentscope", "patient-privacy", null)))
.addPolicy(new Consent.ConsentPolicyComponent().setUri(CONSENT_POLICY_SENSITIVE_DATA_URI))
.setPatient(new Reference(PATIENT_ID))
.addPerformer(new Reference(PATIENT_ID));
myServer.getRestfulServer().registerProvider(theMemberMatchR4ResourceProvider);
myMemberMatcherR4Helper.setRegularFilterSupported(false);
}
private void createCoverageWithBeneficiary(
boolean theAssociateBeneficiaryPatient, boolean includeBeneficiaryIdentifier) {
Patient member = new Patient();
if (theAssociateBeneficiaryPatient) {
// Patient
member.setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily("Person")))
.setBirthDateElement(new DateType("2020-01-01"))
.setId(PATIENT_ID);
if (includeBeneficiaryIdentifier) {
member.setIdentifier(Collections.singletonList(new Identifier()
.setSystem(EXISTING_COVERAGE_PATIENT_IDENT_SYSTEM).setValue(EXISTING_COVERAGE_PATIENT_IDENT_VALUE)));
}
member.setActive(true);
myClient.update().resource(member).execute();
}
// Coverage
ourExistingCoverageIdentifier = new Identifier()
.setSystem(EXISTING_COVERAGE_IDENT_SYSTEM).setValue(EXISTING_COVERAGE_IDENT_VALUE);
Coverage ourExistingCoverage = (Coverage) new Coverage()
.setStatus(Coverage.CoverageStatus.ACTIVE)
.setIdentifier(Collections.singletonList(ourExistingCoverageIdentifier));
if (theAssociateBeneficiaryPatient) {
ourExistingCoverage.setBeneficiary(new Reference(member))
.setId(EXISTING_COVERAGE_ID);
}
myClient.create().resource(ourExistingCoverage).execute().getId().toUnqualifiedVersionless().getValue();
}
@Test
public void testCoverageNoBeneficiaryReturns422() throws Exception {
createCoverageWithBeneficiary(false, false);
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Could not find beneficiary for coverage.");
}
@Test
public void testCoverageBeneficiaryNoIdentifierReturns422() throws Exception {
createCoverageWithBeneficiary(true, false);
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Coverage beneficiary does not have an identifier.");
}
@Test
public void testCoverageNoMatchingPatientFamilyNameReturns422() throws Exception {
createCoverageWithBeneficiary(true, true);
myPatient.setName(Lists.newArrayList(new HumanName().setUse(HumanName.NameUse.OFFICIAL).setFamily("Smith")));
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Could not find matching patient for coverage.");
}
@Test
public void testCoverageNoMatchingPatientBirthdateReturns422() throws Exception {
createCoverageWithBeneficiary(true, false);
myPatient.setBirthDateElement(new DateType("2000-01-01"));
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Could not find matching patient for coverage.");
}
@Test
public void testRegularContentProfileAccessWithRegularNotAllowedReturns422() throws Exception {
createCoverageWithBeneficiary(true, true);
myConsent.getPolicy().get(0).setUri(CONSENT_POLICY_REGULAR_DATA_URI);
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Consent policy does not match the data release segmentation capabilities.");
}
@Test
public void testSensitiveContentProfileAccessWithRegularNotAllowed() throws Exception {
createCoverageWithBeneficiary(true, true);
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
Parameters parametersResponse = performOperation(myServerBase + ourQuery,
EncodingEnum.JSON, inputParameters);
validateMemberIdentifier(parametersResponse);
}
@Test
public void testRegularContentProfileAccessWithRegularAllowed() throws Exception {
createCoverageWithBeneficiary(true, true);
myConsent.getPolicy().get(0).setUri(CONSENT_POLICY_REGULAR_DATA_URI);
myMemberMatcherR4Helper.setRegularFilterSupported(true);
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
Parameters parametersResponse = performOperation(myServerBase + ourQuery,
EncodingEnum.JSON, inputParameters);
validateMemberIdentifier(parametersResponse);
}
/**
* Validates that second resource from the response is same as the received coverage
*/
private void validateNewCoverage(Parameters theResponse, Coverage theOriginalCoverage) {
List<IBase> patientList = ParametersUtil.getNamedParameters(this.getFhirContext(), theResponse, COVERAGE_TO_LINK);
assertEquals(1, patientList.size());
Coverage respCoverage = (Coverage) theResponse.getParameter().get(1).getResource();
assertEquals("Coverage/" + theOriginalCoverage.getId(), respCoverage.getId());
assertEquals(theOriginalCoverage.getIdentifierFirstRep().getSystem(), respCoverage.getIdentifierFirstRep().getSystem());
assertEquals(theOriginalCoverage.getIdentifierFirstRep().getValue(), respCoverage.getIdentifierFirstRep().getValue());
}
private void validateConsentPatientAndPerformerRef(Patient thePatient, Consent theConsent) {
String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue();
assertEquals(patientRef, theConsent.getPatient().getReference());
assertEquals(patientRef, theConsent.getPerformer().get(0).getReference());
}
private void validateMemberPatient(Parameters response) {
// parameter MemberPatient must have a new identifier with:
// {
// "use": "usual",
// "type": {
// "coding": [
// {
// "system": "http://terminology.hl7.org/CodeSystem/v2-0203",
// "code": "UMB",
// "display": "Member Number",
// "userSelected": false
// }
// ],
// "text": "Member Number"
// },
// "system": COVERAGE_PATIENT_IDENT_SYSTEM,
// "value": COVERAGE_PATIENT_IDENT_VALUE
// }
List<IBase> patientList = ParametersUtil.getNamedParameters(this.getFhirContext(), response, PARAM_MEMBER_PATIENT);
assertEquals(1, patientList.size());
Patient resultPatient = (Patient) response.getParameter().get(0).getResource();
assertNotNull(resultPatient.getIdentifier());
assertEquals(1, resultPatient.getIdentifier().size());
Identifier addedIdentifier = resultPatient.getIdentifier().get(0);
assertEquals(Identifier.IdentifierUse.USUAL, addedIdentifier.getUse());
checkCoding(addedIdentifier.getType());
assertEquals(EXISTING_COVERAGE_PATIENT_IDENT_SYSTEM, addedIdentifier.getSystem());
assertEquals(EXISTING_COVERAGE_PATIENT_IDENT_VALUE, addedIdentifier.getValue());
}
/**
* validates that the returned patient ID is correct
*/
private void validateMemberIdentifier(Parameters theResponse){
assertTrue(theResponse.hasParameter(PARAM_MEMBER_IDENTIFIER));
Optional<IBase> memberIdentifierOptional = ParametersUtil.getNamedParameter(this.getFhirContext(), theResponse, PARAM_MEMBER_IDENTIFIER);
assertTrue(memberIdentifierOptional.isPresent());
IdType memberIdentifier = (IdType) ((Parameters.ParametersParameterComponent) memberIdentifierOptional.get()).getValue();
assertEquals(PATIENT_ID, memberIdentifier.getValue());
}
@Test
public void testNoCoverageMatchFound() throws Exception {
Parameters inputParameters = buildInputParameters(myPatient, coverageToMatch, coverageToLink, myConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Could not find coverage for member");
}
private Parameters buildInputParameters(Patient thePatient, Coverage theOldCoverage, Coverage theNewCoverage, Consent theConsent) {
Parameters p = new Parameters();
ParametersUtil.addParameterToParameters(this.getFhirContext(), p, PARAM_MEMBER_PATIENT, thePatient);
ParametersUtil.addParameterToParameters(this.getFhirContext(), p, COVERAGE_TO_MATCH, theOldCoverage);
ParametersUtil.addParameterToParameters(this.getFhirContext(), p, COVERAGE_TO_LINK, theNewCoverage);
ParametersUtil.addParameterToParameters(this.getFhirContext(), p, PARAM_CONSENT, theConsent);
return p;
}
private Parameters performOperation(String theUrl,
EncodingEnum theEncoding, Parameters theInputParameters) throws Exception {
HttpPost post = new HttpPost(theUrl);
post.addHeader(Constants.HEADER_ACCEPT_ENCODING, theEncoding.toString());
post.setEntity(new ResourceEntity(this.getFhirContext(), theInputParameters));
ourLog.info("Request: {}", post);
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertEquals(200, response.getStatusLine().getStatusCode());
return theEncoding.newParser(myFhirContext).parseResource(Parameters.class,
responseString);
}
}
private void performOperationExpecting422(String theUrl, EncodingEnum theEncoding,
Parameters theInputParameters, String theExpectedErrorMsg) throws Exception {
HttpPost post = new HttpPost(theUrl);
post.addHeader(Constants.HEADER_ACCEPT_ENCODING, theEncoding.toString());
post.setEntity(new ResourceEntity(this.getFhirContext(), theInputParameters));
ourLog.info("Request: {}", post);
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertEquals(422, response.getStatusLine().getStatusCode());
assertThat(responseString, containsString(theExpectedErrorMsg));
}
}
private void checkCoding(CodeableConcept theType) {
// must match:
// "coding": [
// {
// "system": "http://terminology.hl7.org/CodeSystem/v2-0203",
// "code": "MB",
// "display": "Member Number",
// "userSelected": false
// }
// * ]
Coding coding = theType.getCoding().get(0);
assertEquals("http://terminology.hl7.org/CodeSystem/v2-0203", coding.getSystem());
assertEquals("MB", coding.getCode());
assertEquals("Member Number", coding.getDisplay());
assertFalse(coding.getUserSelected());
}
/**
* Validates that consent from the response is same as the received consent with additional identifier and extension
*/
private Consent validateResponseConsent(Parameters theResponse, Consent theOriginalConsent) {
List<IBase> consentList = ParametersUtil.getNamedParameters(this.getFhirContext(), theResponse, PARAM_CONSENT);
assertEquals(1, consentList.size());
Consent respConsent = (Consent) theResponse.getParameter().get(2).getResource();
assertEquals(theOriginalConsent.getScope().getCodingFirstRep().getSystem(), respConsent.getScope().getCodingFirstRep().getSystem());
assertEquals(theOriginalConsent.getScope().getCodingFirstRep().getCode(), respConsent.getScope().getCodingFirstRep().getCode());
assertEquals(myMemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM, respConsent.getIdentifier().get(0).getSystem());
assertNotNull(respConsent.getIdentifier().get(0).getValue());
return respConsent;
}
@Nested
public class ValidateParameterErrors {
private Patient ourPatient;
private Coverage ourOldCoverage;
private Coverage ourNewCoverage;
private Consent ourConsent;
@BeforeEach
public void beforeValidateParameterErrors() {
ourPatient = new Patient().setGender(Enumerations.AdministrativeGender.FEMALE);
ourOldCoverage = new Coverage();
ourOldCoverage.setId(EXISTING_COVERAGE_ID);
ourNewCoverage = new Coverage();
ourNewCoverage.setId("AA87654");
ourNewCoverage.setIdentifier(Lists.newArrayList(
new Identifier().setSystem("http://newealthplan.example.com").setValue("234567")));
ourConsent = new Consent();
ourConsent.setStatus(Consent.ConsentState.ACTIVE);
}
@Test
public void testInvalidPatient() throws Exception {
Parameters inputParameters = buildInputParameters(new Patient(), ourOldCoverage, ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_MEMBER_PATIENT + "\\\" is required.");
}
@Test
public void testInvalidOldCoverage() throws Exception {
Parameters inputParameters = buildInputParameters(ourPatient, new Coverage(), ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + COVERAGE_TO_MATCH + "\\\" is required.");
}
@Test
public void testInvalidNewCoverage() throws Exception {
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, new Coverage(), ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + COVERAGE_TO_LINK + "\\\" is required.");
}
@Test
public void testInvalidConsent() throws Exception {
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, ourNewCoverage, new Consent());
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_CONSENT + "\\\" is required.");
}
@Test
public void testMissingPatientFamilyName() throws Exception {
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_MEMBER_PATIENT_NAME + "\\\" is required.");
}
@Test
public void testMissingPatientBirthdate() throws Exception {
ourPatient.setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily("Person")));
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_MEMBER_PATIENT_BIRTHDATE + "\\\" is required.");
}
@Test
public void testMissingConsentPatientReference() throws Exception {
ourPatient.setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily("Person")))
.setBirthDateElement(new DateType("2020-01-01"));
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_CONSENT_PATIENT_REFERENCE + "\\\" is required.");
}
@Test
public void testMissingConsentPerformerReference() throws Exception {
ourPatient.setName(Lists.newArrayList(new HumanName()
.setUse(HumanName.NameUse.OFFICIAL).setFamily("Person")))
.setBirthDateElement(new DateType("2020-01-01"));
ourConsent.setPatient(new Reference("Patient/1"));
Parameters inputParameters = buildInputParameters(ourPatient, ourOldCoverage, ourNewCoverage, ourConsent);
performOperationExpecting422(myServerBase + ourQuery, EncodingEnum.JSON, inputParameters,
"Parameter \\\"" + PARAM_CONSENT_PERFORMER_REFERENCE + "\\\" is required.");
}
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -213,11 +213,6 @@ public class ProviderConstants {
*/
public static final String OPERATION_REINDEX_RESPONSE_JOB_ID = "jobId";
/**
* Operation name for the $member-match operation
*/
public static final String OPERATION_MEMBER_MATCH = "$member-match";
/**
* Operation name for the $reindex-terminology operation
*/

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,11 +5,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@ -26,11 +25,11 @@ public class OperationServerInvalidDefinitionTest {
class OperationProvider {
@Operation(name = ProviderConstants.OPERATION_MEMBER_MATCH, idempotent = false, returnParameters = {
@OperationParam(name = "MemberIdentifier", type = StringDt.class)
@Operation(name = ProviderConstants.MDM_MATCH, idempotent = false, returnParameters = {
@OperationParam(name = "matchResult", type = StringDt.class)
})
public Parameters patientMemberMatch(
@OperationParam(name = Constants.PARAM_MEMBER_PATIENT, min = 1, max = 1) Patient theMemberPatient
public Parameters mdmMatch(
@OperationParam(name = ProviderConstants.MDM_MATCH_RESOURCE, min = 1, max = 1) IBaseResource theResource
) {
return null;
}
@ -41,7 +40,7 @@ public class OperationServerInvalidDefinitionTest {
myRestfulServerExtension.registerProvider(new OperationProvider());
fail();
} catch (ConfigurationException e) {
assertEquals("HAPI-0288: Failure scanning class OperationProvider: HAPI-0360: Incorrect use of type StringDt as return type for method when theContext is for version R4 in method: public org.hl7.fhir.r4.model.Parameters ca.uhn.fhir.rest.server.OperationServerInvalidDefinitionTest$1OperationProvider.patientMemberMatch(org.hl7.fhir.r4.model.Patient)", e.getMessage());
assertEquals("HAPI-0288: Failure scanning class OperationProvider: HAPI-0360: Incorrect use of type StringDt as return type for method when theContext is for version R4 in method: public org.hl7.fhir.r4.model.Parameters ca.uhn.fhir.rest.server.OperationServerInvalidDefinitionTest$1OperationProvider.mdmMatch(org.hl7.fhir.instance.model.api.IBaseResource)", e.getMessage());
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

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

View File

@ -9,7 +9,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description>

View File

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

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.11.5-SNAPSHOT</version>
<version>6.11.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.11.5-SNAPSHOT</version>
<version>6.11.6-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>