Add changelog

This commit is contained in:
Tadgh 2021-08-10 14:49:31 -04:00
parent 71ed25b77c
commit fe98162616
6 changed files with 39 additions and 17 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 2876
jira: SMILE-1153
title: "Fixed a bug in transaction bundle processing, specifically for bundles which contained both a conditional create, and a resource which relied on this conditional create as a reference.
This would cause the referring resource to generate a contained resource instead of appropriately referencing the existing patient."

View File

@ -94,6 +94,7 @@ import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction; import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
@ -1162,10 +1163,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
validateResourceForStorage((T) theResource, entity); validateResourceForStorage((T) theResource, entity);
} }
} }
String resourceType = myContext.getResourceType(theResource); if (!StringUtils.isBlank(entity.getResourceType())) {
if (isNotBlank(entity.getResourceType()) && !entity.getResourceType().equals(resourceType)) { validateIncomingResourceTypeMatchesExisting(theResource, entity);
throw new UnprocessableEntityException(
"Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
} }
} }
@ -1206,6 +1205,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) { if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) {
newParams = new ResourceIndexedSearchParams(); newParams = new ResourceIndexedSearchParams();
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing); mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true); changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
@ -1415,6 +1415,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return entity; return entity;
} }
private void validateIncomingResourceTypeMatchesExisting(IBaseResource theResource, ResourceTable entity) {
String resourceType = myContext.getResourceType(theResource);
if (!resourceType.equals(entity.getResourceType())) {
throw new UnprocessableEntityException(
"Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
}
}
@Override @Override
public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion,
IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) { IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) {

View File

@ -262,7 +262,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setCreatedByMatchUrl(theIfNoneExist); entity.setCreatedByMatchUrl(theIfNoneExist);
entity.setVersion(1); entity.setVersion(1);
//FIXME GGG is this possibly where we are fetching the thing?
if (isNotBlank(theIfNoneExist)) { if (isNotBlank(theIfNoneExist)) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest); Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest);
if (match.size() > 1) { if (match.size() > 1) {
@ -279,7 +278,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}; };
Supplier<IIdType> idSupplier = () -> { Supplier<IIdType> idSupplier = () -> {
return myTxTemplate.execute(tx -> { myTxTemplate.execute(tx -> {
IIdType retVal = myIdHelperService.translatePidIdToForcedId(myFhirContext, myResourceName, pid); IIdType retVal = myIdHelperService.translatePidIdToForcedId(myFhirContext, myResourceName, pid);
if (!retVal.hasVersionIdPart()) { if (!retVal.hasVersionIdPart()) {
IIdType idWithVersion = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.RESOURCE_CONDITIONAL_CREATE_VERSION, pid.getIdAsLong()); IIdType idWithVersion = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.RESOURCE_CONDITIONAL_CREATE_VERSION, pid.getIdAsLong());

View File

@ -791,6 +791,8 @@ public abstract class BaseTransactionProcessor {
String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry); String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theTransactionDetails, theRequest); outcome = resourceDao.create(res, matchUrl, false, theTransactionDetails, theRequest);
// IS THIS THE MAGIC SAUCE?
res.setId(outcome.getId());
if (nextResourceId != null) { if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest);
} }
@ -865,7 +867,6 @@ public abstract class BaseTransactionProcessor {
matchUrl = parts.getResourceType(); matchUrl = parts.getResourceType();
} }
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
//TODO FIXME GGG This update call comes back with a contained resource.
outcome = resourceDao.update(res, matchUrl, false, false, theRequest, theTransactionDetails); outcome = resourceDao.update(res, matchUrl, false, false, theRequest, theTransactionDetails);
if (Boolean.TRUE.equals(outcome.getCreated())) { if (Boolean.TRUE.equals(outcome.getCreated())) {
conditionalRequestUrls.put(matchUrl, res.getClass()); conditionalRequestUrls.put(matchUrl, res.getClass());

View File

@ -129,6 +129,7 @@ public class SearchParamWithInlineReferencesExtractor {
partitionId = RequestPartitionId.allPartitions(); partitionId = RequestPartitionId.allPartitions();
} }
//THIS IS THE NEW SPOT
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference); mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();

View File

@ -78,6 +78,7 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -964,6 +965,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return retVal; return retVal;
} }
/**
* Helper function to determine if a set of SPs for a resource uses a resolve as part of its fhir path.
*/
private boolean anySearchParameterUsesResolve(Collection<RuntimeSearchParam> searchParams, RestSearchParameterTypeEnum theSearchParamType) {
return searchParams.stream()
.filter(param -> param.getParamType() != theSearchParamType)
.filter(Objects::nonNull)
.anyMatch(param -> param.getPath().contains("resolve"));
}
/** /**
* HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text * HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text
* (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the * (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the
@ -974,17 +986,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
* if we think there's actually a chance * if we think there's actually a chance
*/ */
private void cleanUpContainedResourceReferences(IBaseResource theResource, RestSearchParameterTypeEnum theSearchParamType, Collection<RuntimeSearchParam> searchParams) { private void cleanUpContainedResourceReferences(IBaseResource theResource, RestSearchParameterTypeEnum theSearchParamType, Collection<RuntimeSearchParam> searchParams) {
boolean havePathWithResolveExpression = myModelConfig.isIndexOnContainedResources(); boolean havePathWithResolveExpression =
for (RuntimeSearchParam nextSpDef : searchParams) { myModelConfig.isIndexOnContainedResources()
if (nextSpDef.getParamType() != theSearchParamType) { || anySearchParameterUsesResolve(searchParams, theSearchParamType);
continue;
}
if (defaultString(nextSpDef.getPath()).contains("resolve")) {
havePathWithResolveExpression = true;
break;
}
}
if (havePathWithResolveExpression) { if (havePathWithResolveExpression) {
//FIXME GGG/JA: At this point, if the Task.basedOn.reference.resource does _not_ have an ID, we will attempt to contain it internally.
myContext.newTerser().containResources(theResource, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); myContext.newTerser().containResources(theResource, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
} }
} }