Merge branch 'master' into issue-2786-upload-terminology-command-add-explicit-current-version-control

This commit is contained in:
juan.marchionatto 2021-07-29 16:21:56 -04:00
commit 264dfee099
45 changed files with 1584 additions and 121 deletions

View File

@ -21,20 +21,29 @@ package ca.uhn.fhir.interceptor.model;
*/
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
public class ReadPartitionIdRequestDetails {
private final String myResourceType;
private final RestOperationTypeEnum myRestOperationType;
private final IIdType myReadResourceId;
private final Object mySearchParams;
private final IBaseResource myConditionalTargetOrNull;
public ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, Object theSearchParams) {
public ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, Object theSearchParams, @Nullable IBaseResource theConditionalTargetOrNull) {
myResourceType = theResourceType;
myRestOperationType = theRestOperationType;
myReadResourceId = theReadResourceId;
mySearchParams = theSearchParams;
myConditionalTargetOrNull = theConditionalTargetOrNull;
}
public IBaseResource getConditionalTargetOrNull() {
return myConditionalTargetOrNull;
}
public String getResourceType() {
@ -55,11 +64,11 @@ public class ReadPartitionIdRequestDetails {
public static ReadPartitionIdRequestDetails forRead(String theResourceType, IIdType theId, boolean theIsVread) {
RestOperationTypeEnum op = theIsVread ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null);
return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null, null);
}
public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, Object theParams) {
return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams);
public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, Object theParams, IBaseResource theConditionalOperationTargetOrNull) {
return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams, theConditionalOperationTargetOrNull);
}
public static ReadPartitionIdRequestDetails forHistory(String theResourceType, IIdType theIdType) {
@ -71,6 +80,6 @@ public class ReadPartitionIdRequestDetails {
} else {
restOperationTypeEnum = RestOperationTypeEnum.HISTORY_SYSTEM;
}
return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null);
return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null, null);
}
}

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2828
title: "PatientIdPartitionInterceptor now supports conditional creates of resources where the resource is in
the patient compartment but the conditional URL does not contain a patietn reference."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2823
title: "Mdm failed to load if any mdm subscription had been deleted, e.g. if $expunge expungeEverything had been run
on the server. This has been corrected."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2826
title: "When Client Id Strategy is set to NOT_ALLOWED, permit system requests to create resources, e.g. SearchParameter
and Subscription resources required by the system."

View File

@ -0,0 +1,3 @@
---
type: add
title: "Upgrade net.java.dev.jna to run docker tests on Mac arm64 M1 machines"

View File

@ -0,0 +1,9 @@
---
type: security
issue: 2835
title: "Addressed the following CVE report by bumping the minor version for Jetty in the root POM:
<ul>
<li>
[CVE-2021-34429](https://github.com/advisories/GHSA-vjv5-gp2w-65vm)
</li>
</ul>"

View File

@ -47,6 +47,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
@ -80,7 +81,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* won't be indexed and searches won't work.
* @param theRequestDetails TODO
*/
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails);
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails);
DaoMethodOutcome create(T theResource, String theIfNoneExist, RequestDetails theRequestDetails);
@ -211,7 +212,21 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
/**
* Search for IDs for processing a match URLs, etc.
*/
Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest);
default Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) {
return searchForIds(theParams, theRequest, null);
}
/**
* Search for IDs for processing a match URLs, etc.
*
* @param theConditionalOperationTargetOrNull If we're searching for IDs in order to satisfy a conditional
* create/update, this is the resource being searched for
* @since 5.5.0
*/
default Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) {
return searchForIds(theParams, theRequest);
}
/**
* Takes a map of incoming raw search parameters and translates/parses them into
@ -249,7 +264,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* @param theForceUpdateVersion Create a new version with the same contents as the current version even if the content hasn't changed (this is mostly useful for
* resources mapping to external content such as external code systems)
*/
DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
/**
* Not supported in DSTU1!
@ -262,10 +277,11 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
/**
* Delete a list of resource Pids
* @param theUrl the original URL that triggered the delete
* @param theResourceIds the ids of the resources to be deleted
*
* @param theUrl the original URL that triggered the delete
* @param theResourceIds the ids of the resources to be deleted
* @param theDeleteConflicts out parameter of conflicts preventing deletion
* @param theRequest the request that initiated the request
* @param theRequest the request that initiated the request
* @return response back to the client
*/
DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest);
@ -273,8 +289,8 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
/**
* Returns the current version ID for the given resource
*/
default String getCurrentVersionId(IIdType theReferenceElement) {
return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart();
}
default String getCurrentVersionId(IIdType theReferenceElement) {
return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart();
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.bulk.export.job;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
@ -101,7 +102,7 @@ public class ResourceToFileWriter implements ItemWriter<List<IBaseResource>> {
IBaseBinary binary = BinaryUtil.newBinary(myFhirContext);
binary.setContentType(Constants.CT_FHIR_NDJSON);
binary.setContent(myOutputStream.toByteArray());
DaoMethodOutcome outcome = myBinaryDao.create(binary, new SystemRequestDetails());
DaoMethodOutcome outcome = myBinaryDao.create(binary, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
return outcome.getResource().getIdElement();
}

View File

@ -47,6 +47,7 @@ import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.patch.FhirPatch;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
@ -125,8 +126,8 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -228,7 +229,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (isNotBlank(theResource.getIdElement().getIdPart())) {
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
String message = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
String message = getMessageSanitized("failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
} else {
// As of DSTU3, ID and version in the body should be ignored for a create/update
@ -305,21 +306,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
createForcedIdIfNeeded(entity, theResource.getIdElement(), true);
serverAssignedId = true;
} else {
switch (getConfig().getResourceClientIdStrategy()) {
case NOT_ALLOWED:
throw new ResourceNotFoundException(
getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart()));
case ALPHANUMERIC:
if (theResource.getIdElement().isIdPartValidLong()) {
throw new InvalidRequestException(
getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
createForcedIdIfNeeded(entity, theResource.getIdElement(), false);
break;
case ANY:
createForcedIdIfNeeded(entity, theResource.getIdElement(), true);
break;
}
validateResourceIdCreation(theResource, theRequest);
boolean createForPureNumericIds = getConfig().getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC;
createForcedIdIfNeeded(entity, theResource.getIdElement(), createForPureNumericIds);
serverAssignedId = false;
}
} else {
@ -407,6 +396,32 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return outcome;
}
void validateResourceIdCreation(T theResource, RequestDetails theRequest) {
DaoConfig.ClientIdStrategyEnum strategy = getConfig().getResourceClientIdStrategy();
if (strategy == DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED) {
if (!isSystemRequest(theRequest)) {
throw new ResourceNotFoundException(
getMessageSanitized("failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart()));
}
}
if (strategy == DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC) {
if (theResource.getIdElement().isIdPartValidLong()) {
throw new InvalidRequestException(
getMessageSanitized("failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
}
}
protected String getMessageSanitized(String theKey, String theIdPart) {
return getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, theKey, theIdPart);
}
private boolean isSystemRequest(RequestDetails theRequest) {
return theRequest instanceof SystemRequestDetails;
}
private IInstanceValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@ -555,7 +570,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
SearchParameterMap paramMap = resourceSearch.getSearchParameterMap();
paramMap.setLoadSynchronous(true);
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequest);
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequest, null);
if (resourceIds.size() > 1) {
if (!getConfig().isAllowMultipleDelete()) {
@ -632,7 +647,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IBaseOperationOutcome oo;
if (deletedResources.isEmpty()) {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl);
String message = getMessageSanitized("unableToDeleteNotFound", theUrl);
String severity = "warning";
String code = "not-found";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
@ -1401,7 +1416,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
cacheControlDirective.parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL));
}
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams, null);
IBundleProvider retVal = mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName(), cacheControlDirective, theRequest, requestPartitionId);
if (retVal instanceof PersistedJpaBundleProvider) {
@ -1476,7 +1491,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) {
public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) {
TransactionDetails transactionDetails = new TransactionDetails();
return myTransactionService.execute(theRequest, transactionDetails, tx -> {
@ -1492,9 +1507,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
HashSet<ResourcePersistentId> retVal = new HashSet<>();
String uuid = UUID.randomUUID().toString();
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams, theConditionalOperationTargetOrNull);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams);
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
while (iter.hasNext()) {
retVal.add(iter.next());
@ -1602,7 +1617,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType resourceId;
if (isNotBlank(theMatchUrl)) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest);
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest, theResource);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
throw new PreconditionFailedException(msg);

View File

@ -91,7 +91,7 @@ public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource
TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>();
if(theSearchParameterMap.containsKey(getSubjectParamName())) {
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
List<List<IQueryParameterType>> patientParams = new ArrayList<>();
if (theSearchParameterMap.get(getPatientParamName()) != null) {

View File

@ -79,7 +79,7 @@ public class FhirResourceDaoPatientDstu2 extends BaseHapiFhirResourceDao<Patient
paramMap.setLoadSynchronous(true);
}
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null);
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId);
}

View File

@ -68,6 +68,13 @@ public class MatchResourceUrlService {
* Note that this will only return a maximum of 2 results!!
*/
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest) {
return processMatchUrl(theMatchUrl, theResourceType, theTransactionDetails, theRequest, null);
}
/**
* Note that this will only return a maximum of 2 results!!
*/
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest, IBaseResource theConditionalOperationTargetOrNull) {
String resourceType = myContext.getResourceType(theResourceType);
String matchUrl = massageForStorage(resourceType, theMatchUrl);
@ -92,7 +99,7 @@ public class MatchResourceUrlService {
}
paramMap.setLoadSynchronousUpTo(2);
Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest);
Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest, theConditionalOperationTargetOrNull);
if (myDaoConfig.isMatchUrlCacheEnabled() && retVal.size() == 1) {
ResourcePersistentId pid = retVal.iterator().next();
@ -124,14 +131,14 @@ public class MatchResourceUrlService {
return existing;
}
public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest) {
public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) {
StopWatch sw = new StopWatch();
IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType);
if (dao == null) {
throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
}
Set<ResourcePersistentId> retVal = dao.searchForIds(theParamMap, theRequest);
Set<ResourcePersistentId> retVal = dao.searchForIds(theParamMap, theRequest, theConditionalOperationTargetOrNull);
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) {

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObse
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}

View File

@ -74,7 +74,7 @@ public class FhirResourceDaoPatientDstu3 extends BaseHapiFhirResourceDao<Patient
paramMap.setLoadSynchronous(true);
}
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null);
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId);
}

View File

@ -53,7 +53,7 @@ public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObserva
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}

View File

@ -74,7 +74,7 @@ public class FhirResourceDaoPatientR4 extends BaseHapiFhirResourceDao<Patient>im
paramMap.setLoadSynchronous(true);
}
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null);
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId);
}

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObserva
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}

View File

@ -74,7 +74,7 @@ public class FhirResourceDaoPatientR5 extends BaseHapiFhirResourceDao<Patient> i
paramMap.setLoadSynchronous(true);
}
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null);
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId);
}

View File

@ -92,7 +92,7 @@ public class DeleteExpungeJobSubmitterImpl implements IDeleteExpungeJobSubmitter
List<RequestPartitionId> retval = new ArrayList<>();
for (String url : theUrlsToDeleteExpunge) {
ResourceSearch resourceSearch = myMatchUrlService.getResourceSearch(url);
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, resourceSearch.getResourceName(), null);
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, resourceSearch.getResourceName(), null, null);
retval.add(requestPartitionId);
}
return retval;

View File

@ -118,17 +118,6 @@ public class PatientIdPartitionInterceptor {
return provideCompartmentMemberInstanceResponse(theRequestDetails, compartmentIdentity);
}
@Nonnull
private List<RuntimeSearchParam> getCompartmentSearchParams(RuntimeResourceDefinition resourceDef) {
return resourceDef
.getSearchParams()
.stream()
.filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
.filter(param -> param.getProvidesMembershipInCompartments() != null && param.getProvidesMembershipInCompartments().contains("Patient"))
.collect(Collectors.toList());
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
public RequestPartitionId identifyForRead(ReadPartitionIdRequestDetails theReadDetails, RequestDetails theRequestDetails) {
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theReadDetails.getResourceType());
@ -169,7 +158,23 @@ public class PatientIdPartitionInterceptor {
// nothing
}
return provideUnsupportedQueryResponse(theReadDetails);
// If we couldn't identify a patient ID by the URL, let's try using the
// conditional target if we have one
if (theReadDetails.getConditionalTargetOrNull() != null) {
return identifyForCreate(theReadDetails.getConditionalTargetOrNull(), theRequestDetails);
}
return provideNonPatientSpecificQueryResponse(theReadDetails);
}
@Nonnull
private List<RuntimeSearchParam> getCompartmentSearchParams(RuntimeResourceDefinition resourceDef) {
return resourceDef
.getSearchParams()
.stream()
.filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
.filter(param -> param.getProvidesMembershipInCompartments() != null && param.getProvidesMembershipInCompartments().contains("Patient"))
.collect(Collectors.toList());
}
private String getSingleResourceIdValueOrNull(SearchParameterMap theParams, String theParamName, String theResourceType) {
@ -206,8 +211,8 @@ public class PatientIdPartitionInterceptor {
/**
* Return a partition or throw an error for FHIR operations that can not be used with this interceptor
*/
protected RequestPartitionId provideUnsupportedQueryResponse(ReadPartitionIdRequestDetails theRequestDetails) {
throw new MethodNotAllowedException("This server is not able to handle this request of type " + theRequestDetails.getRestOperationType());
protected RequestPartitionId provideNonPatientSpecificQueryResponse(ReadPartitionIdRequestDetails theRequestDetails) {
return RequestPartitionId.allPartitions();
}

View File

@ -27,13 +27,13 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistryController;
@ -63,6 +63,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.hl7.fhir.utilities.npm.NpmPackage;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
@ -358,16 +359,23 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
private IBundleProvider searchResource(IFhirResourceDao theDao, SearchParameterMap theMap) {
if (myPartitionSettings.isPartitioningEnabled()) {
SystemRequestDetails requestDetails = new SystemRequestDetails();
SystemRequestDetails requestDetails = newSystemRequestDetails();
return theDao.search(theMap, requestDetails);
} else {
return theDao.search(theMap);
}
}
@Nonnull
private SystemRequestDetails newSystemRequestDetails() {
return
new SystemRequestDetails()
.setRequestPartitionId(RequestPartitionId.defaultPartition());
}
private void createResource(IFhirResourceDao theDao, IBaseResource theResource) {
if (myPartitionSettings.isPartitioningEnabled()) {
SystemRequestDetails requestDetails = new SystemRequestDetails();
SystemRequestDetails requestDetails = newSystemRequestDetails();
theDao.create(theResource, requestDetails);
} else {
theDao.create(theResource);
@ -376,7 +384,7 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
private DaoMethodOutcome updateResource(IFhirResourceDao theDao, IBaseResource theResource) {
if (myPartitionSettings.isPartitioningEnabled()) {
SystemRequestDetails requestDetails = new SystemRequestDetails();
SystemRequestDetails requestDetails = newSystemRequestDetails();
return theDao.update(theResource, requestDetails);
} else {
return theDao.update(theResource);

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.partition;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -44,8 +45,8 @@ public interface IRequestPartitionHelperSvc {
}
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams);
default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams, IBaseResource theConditionalOperationTargetOrNull) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, theConditionalOperationTargetOrNull);
return determineReadPartitionForRequest(theRequest, theResourceType, details);
}

View File

@ -109,14 +109,14 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
if (myPartitionSettings.isPartitioningEnabled()) {
// Handle system requests
//TODO GGG eventually, theRequest will not be allowed to be null here, and we will pass through SystemRequestDetails instead.
if (theRequest == null && nonPartitionableResource) {
if ((theRequest == null || theRequest instanceof SystemRequestDetails) && nonPartitionableResource) {
return RequestPartitionId.defaultPartition();
}
if (theRequest instanceof SystemRequestDetails) {
if (theRequest instanceof SystemRequestDetails && systemRequestHasExplicitPartition((SystemRequestDetails) theRequest)) {
requestPartitionId = getSystemRequestPartitionId((SystemRequestDetails) theRequest, nonPartitionableResource);
// Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
} else if (hasHooks(Pointcut.STORAGE_PARTITION_IDENTIFY_READ, myInterceptorBroadcaster, theRequest)) {
// Interceptor call: STORAGE_PARTITION_IDENTIFY_READ
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
@ -186,15 +186,16 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
boolean nonPartitionableResource = myNonPartitionableResourceNames.contains(theResourceType);
//TODO GGG eventually, theRequest will not be allowed to be null here, and we will pass through SystemRequestDetails instead.
if (theRequest == null && nonPartitionableResource) {
if ((theRequest == null || theRequest instanceof SystemRequestDetails) && nonPartitionableResource) {
return RequestPartitionId.defaultPartition();
}
if (theRequest instanceof SystemRequestDetails) {
if (theRequest instanceof SystemRequestDetails && systemRequestHasExplicitPartition((SystemRequestDetails) theRequest)) {
requestPartitionId = getSystemRequestPartitionId((SystemRequestDetails) theRequest, nonPartitionableResource);
} else {
//This is an external Request (e.g. ServletRequestDetails) so we want to figure out the partition via interceptor.
HookParams params = new HookParams()// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE
HookParams params = new HookParams()
.add(IBaseResource.class, theResource)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
@ -215,6 +216,10 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
return RequestPartitionId.allPartitions();
}
private boolean systemRequestHasExplicitPartition(@Nonnull SystemRequestDetails theRequest) {
return theRequest.getRequestPartitionId() != null || theRequest.getTenantId() != null;
}
@Nonnull
@Override
public PartitionablePartitionId toStoragePartition(@Nonnull RequestPartitionId theRequestPartitionId) {

View File

@ -72,8 +72,9 @@ public class SystemRequestDetails extends RequestDetails {
return myRequestPartitionId;
}
public void setRequestPartitionId(RequestPartitionId theRequestPartitionId) {
public SystemRequestDetails setRequestPartitionId(RequestPartitionId theRequestPartitionId) {
myRequestPartitionId = theRequestPartitionId;
return this;
}
@Override

View File

@ -271,7 +271,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
String resourceType = search.getResourceType();
SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params, null);
SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, requestPartitionId);
myIdToSearchTask.put(search.getUuid(), task);
myExecutor.submit(task);

View File

@ -814,7 +814,9 @@ public class SearchBuilder implements ISearchBuilder {
if (findVersionFieldName != null) {
sqlBuilder.append(", r." + findVersionFieldName);
}
sqlBuilder.append(" FROM ResourceLink r WHERE r.");
sqlBuilder.append(" FROM ResourceLink r WHERE ");
sqlBuilder.append("r.");
sqlBuilder.append(searchPidFieldName);
sqlBuilder.append(" IN (:target_pids)");

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.bulk;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
@ -944,7 +945,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test {
public String getBinaryContents(IBulkDataExportSvc.JobInfo theJobInfo, int theIndex) {
// Iterate over the files
Binary nextBinary = myBinaryDao.read(theJobInfo.getFiles().get(theIndex).getResourceId(), new SystemRequestDetails());
Binary nextBinary = myBinaryDao.read(theJobInfo.getFiles().get(theIndex).getResourceId(), new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
assertEquals(Constants.CT_FHIR_NDJSON, nextBinary.getContentType());
String nextContents = new String(nextBinary.getContent(), Constants.CHARSET_UTF8);
ourLog.info("Next contents for type {}:\n{}", nextBinary.getResourceType(), nextContents);
@ -1238,12 +1239,12 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test {
createCareTeamWithIndex(i, patId);
}
myPatientGroupId = myGroupDao.update(group, new SystemRequestDetails()).getId();
myPatientGroupId = myGroupDao.update(group, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())).getId();
//Manually create another golden record
Patient goldenPatient2 = new Patient();
goldenPatient2.setId("PAT888");
DaoMethodOutcome g2Outcome = myPatientDao.update(goldenPatient2, new SystemRequestDetails());
DaoMethodOutcome g2Outcome = myPatientDao.update(goldenPatient2, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
Long goldenPid2 = myIdHelperService.getPidOrNull(g2Outcome.getResource());
//Create some nongroup patients MDM linked to a different golden resource. They shouldnt be included in the query.
@ -1272,14 +1273,14 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test {
patient.setGender(i % 2 == 0 ? Enumerations.AdministrativeGender.MALE : Enumerations.AdministrativeGender.FEMALE);
patient.addName().setFamily("FAM" + i);
patient.addIdentifier().setSystem("http://mrns").setValue("PAT" + i);
return myPatientDao.update(patient, new SystemRequestDetails());
return myPatientDao.update(patient, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
}
private void createCareTeamWithIndex(int i, IIdType patId) {
CareTeam careTeam = new CareTeam();
careTeam.setId("CT" + i);
careTeam.setSubject(new Reference(patId)); // This maps to the "patient" search parameter on CareTeam
myCareTeamDao.update(careTeam, new SystemRequestDetails());
myCareTeamDao.update(careTeam, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
}
private void createImmunizationWithIndex(int i, IIdType patId) {
@ -1297,7 +1298,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test {
cc.addCoding().setSystem("vaccines").setCode("COVID-19");
immunization.setVaccineCode(cc);
}
myImmunizationDao.update(immunization, new SystemRequestDetails());
myImmunizationDao.update(immunization, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
}
private void createObservationWithIndex(int i, IIdType patId) {
@ -1308,7 +1309,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test {
if (patId != null) {
obs.getSubject().setReference(patId.getValue());
}
myObservationDao.update(obs, new SystemRequestDetails());
myObservationDao.update(obs, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition()));
}
public void linkToGoldenResource(Long theGoldenPid, Long theSourcePid) {

View File

@ -0,0 +1,77 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
class BaseHapiFhirResourceDaoTest {
TestResourceDao mySvc = new TestResourceDao();
@Test
public void validateResourceIdCreation_asSystem() {
Patient patient = new Patient();
RequestDetails sysRequest = new SystemRequestDetails();
mySvc.getConfig().setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED);
mySvc.validateResourceIdCreation(patient, sysRequest);
// no exception is thrown
}
@Test
public void validateResourceIdCreation_asUser() {
Patient patient = new Patient();
RequestDetails sysRequest = new ServletRequestDetails();
mySvc.getConfig().setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED);
try {
mySvc.validateResourceIdCreation(patient, sysRequest);
fail();
} catch (ResourceNotFoundException e) {
assertEquals("failedToCreateWithClientAssignedIdNotAllowed", e.getMessage());
}
}
@Test
public void validateResourceIdCreationAlpha_withNumber() {
Patient patient = new Patient();
patient.setId("2401");
RequestDetails sysRequest = new ServletRequestDetails();
mySvc.getConfig().setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC);
try {
mySvc.validateResourceIdCreation(patient, sysRequest);
fail();
} catch (InvalidRequestException e) {
assertEquals("failedToCreateWithClientAssignedNumericId", e.getMessage());
}
}
@Test
public void validateResourceIdCreationAlpha_withAlpha() {
Patient patient = new Patient();
patient.setId("P2401");
RequestDetails sysRequest = new ServletRequestDetails();
mySvc.getConfig().setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC);
mySvc.validateResourceIdCreation(patient, sysRequest);
// no exception is thrown
}
static class TestResourceDao extends BaseHapiFhirResourceDao<Patient> {
private final DaoConfig myDaoConfig = new DaoConfig();
@Override
public DaoConfig getConfig() {
return myDaoConfig;
}
@Override
protected String getMessageSanitized(String theKey, String theIdPart) {
return theKey;
}
}
}

View File

@ -506,6 +506,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
myPagingProvider.setDefaultPageSize(BasePagingProvider.DEFAULT_DEFAULT_PAGE_SIZE);
myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE);
myPartitionSettings.setPartitioningEnabled(false);
}
@AfterEach

View File

@ -49,6 +49,7 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
@ -566,12 +567,10 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
/*
* 20 should be prefetched since that's the initial page size
*/
await().until(() -> {
return runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound() == 20;
});
});
await().until(() -> runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound();
}), equalTo(20));
runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
assertEquals(20, search.getNumFound());

View File

@ -4,15 +4,24 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4SystemTest;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.util.MultimapCollector;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.ExplanationOfBenefit;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
@ -22,8 +31,18 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -225,11 +244,9 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest {
createObservationB();
myCaptureQueriesListener.clear();
try {
myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd);
} catch (MethodNotAllowedException e) {
assertEquals("This server is not able to handle this request of type SEARCH_TYPE", e.getMessage());
}
myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd);
myCaptureQueriesListener.logSelectQueries();
assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE ((t0.RES_TYPE = 'Observation') AND (t0.RES_DELETED_AT IS NULL)) limit '10'", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false));
}
@Test
@ -314,6 +331,139 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest {
assertThat(myCaptureQueriesListener.getSelectQueries().get(1).getSql(false, false), containsString("PARTITION_ID="));
}
@Test
public void testTransaction_NoRequestDetails() throws IOException {
Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json");
// Maybe in the future we'll make request details mandatory and if that
// causes this to fail that's ok
Bundle outcome = mySystemDao.transaction(null, input);
ListMultimap<String, String> resourceIds = outcome
.getEntry()
.stream()
.collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue()));
Multimap<String, Integer> resourcesByType = runInTransaction(() -> {
logAllResources();
return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId()));
});
assertThat(resourcesByType.get("Patient"), contains(4267));
assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267));
assertThat(resourcesByType.get("Coverage"), contains(4267));
assertThat(resourcesByType.get("Organization"), contains(-1, -1));
assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1));
}
@Test
public void testTransaction_SystemRequestDetails() throws IOException {
Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json");
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(new SystemRequestDetails(), input);
myCaptureQueriesListener.logSelectQueries();
List<String> selectQueryStrings = myCaptureQueriesListener
.getSelectQueries()
.stream()
.map(t -> t.getSql(false, false).toUpperCase(Locale.US))
.filter(t -> !t.contains("FROM HFJ_TAG_DEF"))
.collect(Collectors.toList());
for (String next : selectQueryStrings) {
assertThat(next, either(containsString("PARTITION_ID =")).or(containsString("PARTITION_ID IN")));
}
ListMultimap<String, String> resourceIds = outcome
.getEntry()
.stream()
.collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue()));
String patientId = resourceIds.get("Patient").get(0);
Multimap<String, Integer> resourcesByType = runInTransaction(() -> {
logAllResources();
return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId()));
});
assertThat(resourcesByType.get("Patient"), contains(4267));
assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267));
assertThat(resourcesByType.get("Coverage"), contains(4267));
assertThat(resourcesByType.get("Organization"), contains(-1, -1));
assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1));
// Try Searching
SearchParameterMap map = new SearchParameterMap();
map.add(ExplanationOfBenefit.SP_PATIENT, new ReferenceParam(patientId));
map.addInclude(new Include("*"));
myCaptureQueriesListener.clear();
IBundleProvider result = myExplanationOfBenefitDao.search(map);
List<String> resultIds = toUnqualifiedVersionlessIdValues(result);
assertThat(resultIds.toString(), resultIds, containsInAnyOrder(
resourceIds.get("Coverage").get(0),
resourceIds.get("Organization").get(0),
resourceIds.get("ExplanationOfBenefit").get(0),
resourceIds.get("Patient").get(0),
resourceIds.get("Practitioner").get(0),
resourceIds.get("Practitioner").get(1),
resourceIds.get("Practitioner").get(2)
));
myCaptureQueriesListener.logSelectQueries();
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
assertThat(selectQueries.get(0).getSql(true, false).toUpperCase(Locale.US), matchesPattern("SELECT.*FROM HFJ_RES_LINK.*WHERE.*PARTITION_ID = '4267'.*"));
}
@Test
public void testSearch() throws IOException {
Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json");
Bundle outcome = mySystemDao.transaction(new SystemRequestDetails(), input);
ListMultimap<String, String> resourceIds = outcome
.getEntry()
.stream()
.collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue()));
String patientId = resourceIds.get("Patient").get(0);
Multimap<String, Integer> resourcesByType = runInTransaction(() -> {
logAllResources();
return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId()));
});
assertThat(resourcesByType.get("Patient"), contains(4267));
assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267));
assertThat(resourcesByType.get("Coverage"), contains(4267));
assertThat(resourcesByType.get("Organization"), contains(-1, -1));
assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1));
// Try Searching
SearchParameterMap map = new SearchParameterMap();
map.add(ExplanationOfBenefit.SP_PATIENT, new ReferenceParam(patientId));
map.addInclude(new Include("*"));
myCaptureQueriesListener.clear();
IBundleProvider result = myExplanationOfBenefitDao.search(map);
List<String> resultIds = toUnqualifiedVersionlessIdValues(result);
assertThat(resultIds.toString(), resultIds, containsInAnyOrder(
resourceIds.get("Coverage").get(0),
resourceIds.get("Organization").get(0),
resourceIds.get("ExplanationOfBenefit").get(0),
resourceIds.get("Patient").get(0),
resourceIds.get("Practitioner").get(0),
resourceIds.get("Practitioner").get(1),
resourceIds.get("Practitioner").get(2)
));
myCaptureQueriesListener.logSelectQueries();
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
assertThat(selectQueries.get(0).getSql(true, false).toUpperCase(Locale.US), matchesPattern("SELECT.*FROM HFJ_RES_LINK.*WHERE.*PARTITION_ID = '4267'.*"));
}
@Test
public void testHistory_Type() {
myOrganizationDao.history(null, null, null, mySrd);

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
@ -40,6 +41,8 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
private IInterceptorService myInterceptorService;
@Autowired
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
@AfterEach
public void disablePartitioning() {
@ -75,7 +78,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
public void testSaveAndDeletePackagePartitionsEnabled() throws IOException {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setDefaultPartitionId(1);
myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor());
myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor(myFhirCtx, mySearchParamExtractor));
myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor);
try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/basisprofil.de.tar.gz")) {
@ -109,7 +112,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setDefaultPartitionId(0);
myPartitionSettings.setUnnamedPartitionMode(true);
myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor());
myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor(myFhirCtx, mySearchParamExtractor));
myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor);
try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz")) {

View File

@ -423,9 +423,9 @@ public class NpmR4Test extends BaseJpaR4Test {
myDaoConfig.setAllowExternalReferences(true);
byte[] bytes = loadClasspathBytes("/packages/test-draft-sample.tgz");
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", bytes);
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.onlydrafts/0.11.1", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.onlydrafts").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(0, outcome.getResourcesInstalled().size(), outcome.getResourcesInstalled().toString());

View File

@ -0,0 +1,64 @@
package ca.uhn.fhir.jpa.util;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* Copied from https://stackoverflow.com/questions/23003542/cleanest-way-to-create-a-guava-multimap-from-a-java-8-stream
*/
public class MultimapCollector<T, K, V> implements
Collector<T, ListMultimap<K, V>, ListMultimap<K, V>> {
private final Function<T, K> keyGetter;
private final Function<T, V> valueGetter;
public MultimapCollector(Function<T, K> keyGetter, Function<T, V> valueGetter) {
this.keyGetter = keyGetter;
this.valueGetter = valueGetter;
}
public static <T, K, V> MultimapCollector<T, K, V> toMultimap(Function<T, K> keyGetter, Function<T, V> valueGetter) {
return new MultimapCollector<>(keyGetter, valueGetter);
}
public static <T, K, V> MultimapCollector<T, K, T> toMultimap(Function<T, K> keyGetter) {
return new MultimapCollector<>(keyGetter, v -> v);
}
@Override
public Supplier<ListMultimap<K, V>> supplier() {
return ArrayListMultimap::create;
}
@Override
public BiConsumer<ListMultimap<K, V>, T> accumulator() {
return (map, element) -> map.put(keyGetter.apply(element), valueGetter.apply(element));
}
@Override
public BinaryOperator<ListMultimap<K, V>> combiner() {
return (map1, map2) -> {
map1.putAll(map2);
return map1;
};
}
@Override
public Function<ListMultimap<K, V>, ListMultimap<K, V>> finisher() {
return map -> map;
}
@Override
public Set<Characteristics> characteristics() {
return ImmutableSet.of(Characteristics.IDENTITY_FINISH);
}
}

View File

@ -0,0 +1,986 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "ExplanationOfBenefit",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "payerid"
}
]
},
"system": "https://hl7.org/fhir/sid/payerid",
"value": "5824473976"
},
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "uc"
}
]
},
"system": "https://hl7.org/fhir/sid/claimid",
"value": "1234094"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "professional"
}
]
},
"use": "claim",
"patient": {
"reference": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5"
},
"billablePeriod": {
"start": "2017-01-08",
"end": "2017-01-08"
},
"created": "2017-01-11T00:00:00-08:00",
"insurer": {
"reference": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb"
},
"provider": {
"reference": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d"
},
"payee": {
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/payeetype",
"code": "provider"
}
],
"text": "Claim paid to VENDOR"
},
"party": {
"reference": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d"
}
},
"outcome": "complete",
"disposition": "PAID",
"careTeam": [
{
"sequence": 1,
"provider": {
"reference": "Practitioner/23eccc61-ab67-bf8a-e464-6260f7989556"
},
"responsible": false,
"role": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/claimcareteamrole",
"code": "primary"
}
]
}
},
{
"sequence": 2,
"provider": {
"reference": "Practitioner/dbbc9a06-f685-b481-d739-133755af138e"
},
"responsible": false,
"role": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "referring"
}
]
}
},
{
"sequence": 3,
"provider": {
"reference": "Practitioner/39b9250c-0d01-cbb0-ea89-0de9c74af511"
},
"responsible": true,
"role": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "performing"
}
]
}
}
],
"supportingInfo": [
{
"sequence": 1,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "clmrecvddate"
}
]
},
"timingDate": "2017-01-11"
},
{
"sequence": 2,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "billingnetworkcontractingstatus"
}
]
},
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "other"
}
]
}
}
],
"diagnosis": [
{
"sequence": 4,
"diagnosisCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "I27.2",
"display": "Other secondary pulmonary hypertension"
}
],
"text": "Other secondary pulmonary hypertension"
}
}
],
"procedure": [
{
"sequence": 1,
"date": "2017-01-08T00:00:00-08:00",
"procedureCodeableConcept": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "99233",
"display": "Subsequent hospital care for severe problem"
}
],
"text": "SBSQ HOSPITAL CARE/DAY 35 MINUTES"
}
}
],
"insurance": [
{
"focal": true,
"coverage": {
"reference": "urn:uuid:175dbf4a-7ee2-446d-9938-82eea27871a7"
}
}
],
"item": [
{
"sequence": 1,
"diagnosisSequence": [
4
],
"procedureSequence": [
1
],
"productOrService": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "99233",
"display": "Subsequent hospital care for severe problem"
}
],
"text": "SBSQ HOSPITAL CARE/DAY 35 MINUTES"
},
"servicedPeriod": {
"start": "2017-01-08",
"end": "2017-01-08"
},
"locationCodeableConcept": {
"coding": [
{
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
"code": "99"
}
]
},
"quantity": {
"value": 1,
"unit": "Units",
"system": "http://unitsofmeasure.org",
"code": "[arb'U]"
},
"net": {
"value": 317.00,
"currency": "USD"
},
"adjudication": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 317.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "outofnetwork"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
}
]
}
],
"total": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 317.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 124.69,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0.00,
"currency": "USD"
}
}
],
"payment": {
"date": "2017-02-02",
"amount": {
"value": 0.00,
"currency": "USD"
}
}
},
"request": {
"method": "PUT",
"url": "ExplanationOfBenefit?identifier=5824473976"
}
},
{
"resource": {
"resourceType": "Patient",
"id": "d1a47e2c-509b-e326-deab-597e3f598ca5",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"system": "https://example.org/front-door",
"value": "412563524-CO"
}
],
"name": [
{
"use": "usual",
"text": "HYOHWAN MGUIRRE",
"family": "MGUIRRE",
"given": [
"HYOHWAN"
]
}
],
"telecom": [
{
"system": "phone",
"value": "719-654-0220",
"use": "home"
}
],
"gender": "unknown",
"birthDate": "1958-05-12",
"address": [
{
"use": "home",
"type": "postal",
"line": [
"20360 East 45Th Court",
"PO Box 523"
],
"city": "COLORADO SPRINGS",
"postalCode": "80922-4166"
}
]
},
"request": {
"method": "PUT",
"url": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5"
}
},
{
"fullUrl": "urn:uuid:175dbf4a-7ee2-446d-9938-82eea27871a7",
"resource": {
"resourceType": "Coverage",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/coverageid",
"value": "412563524-CO-80001"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "HMO",
"display": "health maintenance organization policy"
}
],
"text": "HMO - HMO COMMERCIAL-HDHP-Signature"
},
"subscriberId": "412563524",
"beneficiary": {
"reference": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5"
},
"relationship": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
"code": "self",
"display": "Self"
}
],
"text": "The Beneficiary is the Subscriber"
},
"period": {
"start": "2016-01-01",
"end": "2017-07-01"
},
"payor": [
{
"reference": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb"
}
],
"class": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
"code": "group",
"display": "Group"
}
],
"text": "An employee group"
},
"value": "80001",
"name": "CS BRZ HDHP 5500/30%/0 ONX S-NON-MEDICARE"
}
]
},
"request": {
"method": "PUT",
"url": "Coverage?identifier=412563524-CO-80001"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "68ae4f74-afdc-6242-c50e-02ef776d8e5d",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "npi"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "1407833767"
},
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "prov"
}
]
}
],
"name": "PIKES PEAK NEPHROLOGY ASSOCIATES PC",
"address": [
{
"use": "work",
"type": "physical",
"line": [
"1914 LELARAY STREET"
],
"city": "COLORADO SPRINGS",
"postalCode": "80909",
"country": "USA"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "5954a17b-0779-334c-4f1c-e894e45d15fb",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/organizationid",
"value": "NATLTAP CO-KFHP-PAY-CO"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "pay",
"display": "Payer"
}
]
}
],
"name": "KAISER FOUNDATION HEALTHPLAN, INC",
"telecom": [
{
"system": "phone",
"value": "1-800-382-4661",
"use": "work"
}
],
"address": [
{
"use": "work",
"type": "postal",
"line": [
"NATIONAL CLAIMS ADMINISTRATION COLORADO",
"PO Box 629028"
],
"city": "El Dorado Hills",
"state": "CA",
"postalCode": "95762-9028"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "23eccc61-ab67-bf8a-e464-6260f7989556",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "1497983654"
}
],
"name": [
{
"use": "usual",
"text": "CASSIDY, HEATHER M (MD)",
"family": "CASSIDY",
"given": [
"HEATHER"
],
"suffix": [
"MD"
]
}
],
"address": [
{
"use": "work",
"line": [
"Briargate",
"1405 Briargate Pkwy #141"
],
"city": "Colorado Springs",
"postalCode": "80920"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/23eccc61-ab67-bf8a-e464-6260f7989556"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "dbbc9a06-f685-b481-d739-133755af138e",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "1568467280"
},
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "311669909"
}
],
"name": [
{
"use": "usual",
"text": "MOHNSSEN, STEVEN R (MD)",
"family": "MOHNSSEN",
"given": [
"STEVEN"
],
"suffix": [
"MD"
]
}
],
"address": [
{
"use": "work",
"line": [
"1725 E Boulder St",
"Ste 204"
],
"city": "Colorado Springs",
"postalCode": "80909"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/dbbc9a06-f685-b481-d739-133755af138e"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "39b9250c-0d01-cbb0-ea89-0de9c74af511",
"meta": {
"lastUpdated": "2021-06-30",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "1679605265"
},
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "840629252"
}
],
"name": [
{
"use": "usual",
"text": "ROSS, MICHAEL D (MD)",
"family": "ROSS",
"given": [
"MICHAEL"
],
"suffix": [
"MD"
]
}
],
"address": [
{
"use": "work",
"line": [
"1914 Lelaray St"
],
"city": "Colorado Springs",
"postalCode": "80909"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/39b9250c-0d01-cbb0-ea89-0de9c74af511"
}
}
]
}

View File

@ -550,7 +550,7 @@
}
]
},
"system": "https://healthy.kaiserpermanente.org/front-door",
"system": "https://example.org/front-door",
"value": "1000116-GA"
}
],

View File

@ -550,7 +550,7 @@
}
]
},
"system": "https://healthy.kaiserpermanente.org/front-door",
"system": "https://example.org/front-door",
"value": "1000116-GA"
}
],

View File

@ -5,7 +5,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
@ -49,7 +49,7 @@ public class PartitionHelper implements BeforeEachCallback, AfterEachCallback {
private boolean myCalled = false;
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
RequestPartitionId partitionIdentifyRead(ServletRequestDetails theRequestDetails) {
RequestPartitionId partitionIdentifyRead(RequestDetails theRequestDetails) {
myCalled = true;
if (theRequestDetails == null) {
ourLog.info("useful breakpoint :-)");
@ -67,7 +67,7 @@ public class PartitionHelper implements BeforeEachCallback, AfterEachCallback {
}
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
RequestPartitionId partitionIdentifyCreate(ServletRequestDetails theRequestDetails) {
RequestPartitionId partitionIdentifyCreate(RequestDetails theRequestDetails) {
return RequestPartitionId.defaultPartition();
}
}

View File

@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
Logger ourLog = LoggerFactory.getLogger(CqlMeasureEvaluationDstu3Test.class);
private static final Logger ourLog = LoggerFactory.getLogger(CqlMeasureEvaluationDstu3Test.class);
@Autowired
MeasureOperationsProvider myMeasureOperationsProvider;
@ -60,7 +60,7 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
String periodStart = this.getPeriodStart(expected);
String periodEnd = this.getPeriodEnd(expected);
this.ourLog.info("Measure: %s, Patient: %s, Start: %s, End: %s", measureId, patientId, periodStart, periodEnd);
ourLog.info("Measure: {}, Patient: {}, Start: {}, End: {}", measureId, patientId, periodStart, periodEnd);
MeasureReport actual = this.myMeasureOperationsProvider.evaluateMeasure(new IdType("Measure", measureId),
periodStart, periodEnd, null,

View File

@ -22,14 +22,15 @@ package ca.uhn.fhir.jpa.mdm.config;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Subscription;
@ -86,10 +87,10 @@ public class MdmSubscriptionLoader {
}
}
private synchronized void updateIfNotPresent(IBaseResource theSubscription) {
synchronized void updateIfNotPresent(IBaseResource theSubscription) {
try {
mySubscriptionDao.read(theSubscription.getIdElement());
} catch (ResourceNotFoundException e) {
} catch (ResourceNotFoundException | ResourceGoneException e) {
ourLog.info("Creating subsription " + theSubscription.getIdElement());
mySubscriptionDao.update(theSubscription);
}

View File

@ -0,0 +1,64 @@
package ca.uhn.fhir.jpa.mdm.config;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MdmSubscriptionLoaderTest {
@Mock
IFhirResourceDao<IBaseResource> mySubscriptionDao;
@InjectMocks
MdmSubscriptionLoader mySvc = new MdmSubscriptionLoader();
@AfterEach
public void after() {
verifyNoMoreInteractions(mySubscriptionDao);
}
@Test
public void updateIfNotPresent_createSubscriptionIfItWasDeleted() {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenThrow(new ResourceGoneException(""));
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao).update(subscription);
}
@Test
public void updateIfNotPresent_createSubscriptionIfItDoesNotExist() {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenThrow(new ResourceNotFoundException(""));
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao).update(subscription);
}
@Test
public void updateIfNotPresent_createSubscriptionExists() {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenReturn(subscription);
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao, never()).update(any());
}
}

View File

@ -39,7 +39,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.util.VersionEnum;
import org.checkerframework.checker.units.qual.C;
import java.util.Arrays;
import java.util.List;
@ -117,6 +116,12 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
cmpToks.addForeignKey("20210720.4", "FK_IDXCMBTOKNU_RES_ID").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
cmpToks.addIndex("20210720.5", "IDX_IDXCMBTOKNU_STR").unique(false).withColumns("IDX_STRING");
cmpToks.addIndex("20210720.6", "IDX_IDXCMBTOKNU_RES").unique(false).withColumns("RES_ID");
Builder.BuilderWithTableName cmbTokNuTable = version.onTable("HFJ_IDX_CMB_TOK_NU");
cmbTokNuTable.addColumn("20210722.1", "PARTITION_ID").nullable().type(ColumnTypeEnum.INT);
cmbTokNuTable.addColumn("20210722.2", "PARTITION_DATE").nullable().type(ColumnTypeEnum.DATE_ONLY);
cmbTokNuTable.modifyColumn("20210722.3", "RES_ID").nullable().withType(ColumnTypeEnum.LONG);
}
private void init540() {

View File

@ -38,6 +38,7 @@ public class SearchRuntimeDetails {
private String myQueryString;
private SearchStatusEnum mySearchStatus;
private int myFoundIndexMatchesCount;
public SearchRuntimeDetails(RequestDetails theRequestDetails, String theSearchUuid) {
myRequestDetails = theRequestDetails;
mySearchUuid = theSearchUuid;

25
pom.xml
View File

@ -767,7 +767,7 @@
<aries_spifly_version>1.2</aries_spifly_version>
<caffeine_version>2.9.1</caffeine_version>
<commons_codec_version>1.15</commons_codec_version>
<commons_compress_version>1.20</commons_compress_version>
<commons_compress_version>1.21</commons_compress_version>
<commons_text_version>1.9</commons_text_version>
<commons_io_version>2.8.0</commons_io_version>
<commons_lang3_version>3.12.0</commons_lang3_version>
@ -786,8 +786,7 @@
<jaxb_runtime_version>3.0.0</jaxb_runtime_version>
<jena_version>3.17.0</jena_version>
<jersey_version>3.0.0</jersey_version>
<!-- 9.4.17 seems to have issues -->
<jetty_version>9.4.42.v20210604</jetty_version>
<jetty_version>9.4.43.v20210629</jetty_version>
<jsr305_version>3.0.2</jsr305_version>
<junit_version>5.7.1</junit_version>
<flexmark_version>0.50.40</flexmark_version>
@ -822,6 +821,7 @@
<spring_retry_version>1.2.2.RELEASE</spring_retry_version>
<stax2_api_version>3.1.4</stax2_api_version>
<testcontainers_version>1.15.3</testcontainers_version>
<thymeleaf-version>3.0.12.RELEASE</thymeleaf-version>
<woodstox_core_asl_version>4.4.1</woodstox_core_asl_version>
@ -1593,8 +1593,21 @@
<groupId>org.yaml</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<!-- Elastic have repackaged net.java.dev.jna, but we need a newer version to support Mac ARM. Managed below. -->
<groupId>org.elasticsearch</groupId>
<artifactId>jna</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Upgrade testcontainers and elasticsearch dependency for Mac M1 ARM64 support.
We can delete this once testcontainers and elasticsearch upgrade. -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-elasticsearch</artifactId>
@ -1876,19 +1889,19 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.15.3</version>
<version>${testcontainers_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>elasticsearch</artifactId>
<version>1.15.3</version>
<version>${testcontainers_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.15.3</version>
<version>${testcontainers_version}</version>
<scope>test</scope>
</dependency>
<dependency>