Patient ID Partition Mode should support conditional create (#2828)

* Fixes

* Test fix

* Add changelog

* Test fixes

* Test fixes

* Docs tweak

* Partitioning fixes

* Import fix

* Fix test data
This commit is contained in:
James Agnew 2021-07-26 14:18:58 -04:00 committed by GitHub
parent 6bbb403fd1
commit f957661dda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1358 additions and 91 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

@ -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;
@ -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
@ -262,6 +277,7 @@ 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 theDeleteConflicts out parameter of conflicts preventing deletion

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

@ -126,6 +126,7 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
@ -569,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()) {
@ -1415,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) {
@ -1490,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 -> {
@ -1506,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());
@ -1616,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;
@ -365,6 +366,13 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
}
}
@Nonnull
private SystemRequestDetails newSystemRequestDetails() {
return
new SystemRequestDetails()
.setRequestPartitionId(RequestPartitionId.defaultPartition());
}
private void createResource(IFhirResourceDao theDao, IBaseResource theResource) {
if (myPartitionSettings.isPartitioningEnabled()) {
SystemRequestDetails requestDetails = newSystemRequestDetails();

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

@ -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(() -> {
await().until(() -> runInTransaction(() -> {
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException(""));
return search.getNumFound() == 20;
});
});
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());
}
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

@ -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;