Jd 20241126 fix versioned refs in transaction bundles (#6521)
* tests, fix, minor refactoring * spotless * changelog * changelog update --------- Co-authored-by: jdar <justin.dar@smiledigitalhealth.com>
This commit is contained in:
parent
e59e7fc29f
commit
6c4aa2c154
|
@ -23,12 +23,14 @@ import ca.uhn.fhir.parser.IParser;
|
|||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This object supplies default configuration to all {@link IParser parser} instances
|
||||
|
@ -126,6 +128,17 @@ public class ParserOptions {
|
|||
return myDontStripVersionsFromReferencesAtPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sub-collection of {@link #getDontStripVersionsFromReferencesAtPaths()} containing only paths
|
||||
* for the given resource type.
|
||||
*/
|
||||
public Set<String> getDontStripVersionsFromReferencesAtPathsByResourceType(String theResourceType) {
|
||||
Validate.notEmpty(theResourceType, "theResourceType must not be null or empty");
|
||||
return myDontStripVersionsFromReferencesAtPaths.stream()
|
||||
.filter(referencePath -> referencePath.startsWith(theResourceType + "."))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* If supplied value(s), any resource references at the specified paths will have their
|
||||
* resource versions encoded instead of being automatically stripped during the encoding
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6519
|
||||
jira: SMILE-8776
|
||||
title: "Previously, when posting a transaction bundle with versioned references, the server would strip versioned
|
||||
references form the resource even when configured not to do so. This has now been fixed."
|
|
@ -26,6 +26,7 @@ import org.hl7.fhir.r4.model.MessageHeader;
|
|||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Provenance;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.Task;
|
||||
|
@ -42,6 +43,8 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -65,6 +68,8 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
|||
myStorageSettings.setDeleteEnabled(new JpaStorageSettings().isDeleteEnabled());
|
||||
myStorageSettings.setRespectVersionsForSearchIncludes(new JpaStorageSettings().isRespectVersionsForSearchIncludes());
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths(new JpaStorageSettings().getAutoVersionReferenceAtPaths());
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(new JpaStorageSettings().isAutoCreatePlaceholderReferenceTargets());
|
||||
myStorageSettings.setResourceClientIdStrategy(new JpaStorageSettings().getResourceClientIdStrategy());
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
@ -667,6 +672,88 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
|||
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontStripVersionsFromRefsAtPaths_inTransactionBundle_shouldPreserveVersion() {
|
||||
Set<String> referencePaths = Set.of("AuditEvent.entity.what","MessageHeader.focus","Provenance.target","Provenance.entity.what");
|
||||
|
||||
myFhirContext.getParserOptions().setDontStripVersionsFromReferencesAtPaths(referencePaths);
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths("Encounter.subject");
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
|
||||
postBundleAndAssertProvenanceRefsPreserved("/transaction-bundles/transaction-with-client-supplied-versioned-reference.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontStripVersionsFromRefsAtAllPaths_inTransactionBundle_shouldPreserveVersion() {
|
||||
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths("Encounter.subject");
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
|
||||
postBundleAndAssertProvenanceRefsPreserved("/transaction-bundles/transaction-with-client-supplied-versioned-reference.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontStripVersionsFromRefsAtPaths_withTransactionBundleAndAutoVersionSet_shouldPreserveVersion() {
|
||||
Set<String> referencePaths = Set.of("AuditEvent.entity.what","MessageHeader.focus","Provenance.target","Provenance.entity.what");
|
||||
|
||||
myFhirContext.getParserOptions().setDontStripVersionsFromReferencesAtPaths(referencePaths);
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths("Provenance.entity.what");
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setId("242976");
|
||||
myEncounterDao.update(encounter);
|
||||
|
||||
postBundleAndAssertProvenanceRefsPreserved("/transaction-bundles/transaction-with-client-supplied-versioned-reference.json");
|
||||
}
|
||||
|
||||
private Provenance postBundleAndAssertProvenanceRefsPreserved(String theBundlePath) {
|
||||
Bundle bundle = myFhirContext.newJsonParser().parseResource(Bundle.class,
|
||||
new InputStreamReader(
|
||||
FhirResourceDaoR4VersionedReferenceTest.class.getResourceAsStream(theBundlePath)));
|
||||
|
||||
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), bundle);
|
||||
|
||||
Optional<Bundle.BundleEntryComponent> provenanceEntry = transaction.getEntry().stream().filter(entry -> entry.getResponse().getLocation().contains("Provenance")).findFirst();
|
||||
assertThat(provenanceEntry).isPresent();
|
||||
String provenanceLocation = provenanceEntry.get().getResponse().getLocation();
|
||||
Provenance provenance = myProvenanceDao.read(new IdDt(provenanceLocation));
|
||||
assertThat(provenance.getEntity().get(0).getWhat().getReference()).isEqualTo("Encounter/242976/_history/1");
|
||||
return provenance;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontStripVersionsFromRefsAtPathsAndAutoVersionSameTransaction_withTransactionBundle_shouldPreserveVersion() {
|
||||
Set<String> referencePaths = Set.of("AuditEvent.entity.what","MessageHeader.focus","Provenance.target","Provenance.entity.what", "Provenance.agent.who");
|
||||
|
||||
myFhirContext.getParserOptions().setDontStripVersionsFromReferencesAtPaths(referencePaths);
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths("Provenance.agent.who");
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
|
||||
Provenance provenance = postBundleAndAssertProvenanceRefsPreserved("/transaction-bundles/transaction-with-client-assigned-version-reference-and-auto-version-field.json");
|
||||
assertThat(provenance.getAgent().get(0).getWho().getReference()).isEqualTo("Patient/237643/_history/1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontStripVersionsFromRefsAtPaths_withTransactionBundleAndPreExistingReference_shouldPreserveVersion() {
|
||||
Set<String> referencePaths = Set.of("AuditEvent.entity.what","MessageHeader.focus","Provenance.target","Provenance.entity.what");
|
||||
|
||||
myFhirContext.getParserOptions().setDontStripVersionsFromReferencesAtPaths(referencePaths);
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths("Provenance.entity.what");
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setId("242976");
|
||||
myEncounterDao.update(encounter);
|
||||
|
||||
postBundleAndAssertProvenanceRefsPreserved("/transaction-bundles/transaction-with-client-supplied-versioned-reference-and-pre-existing-ref.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontOverwriteExistingVersion() {
|
||||
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "https://smilecdrmock.harrisarc.ca/fhir-system/Encounter/242976",
|
||||
"resource": {
|
||||
"resourceType": "Encounter",
|
||||
"id": "242976",
|
||||
"meta": {
|
||||
"versionId": "4",
|
||||
"lastUpdated": "2024-07-26T10:43:49.287-04:00",
|
||||
"source": "#d9d63c433c828a4d"
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "official",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "VN"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://www.ciussscentreouest.ca/ids/visit-number/iclsc/dlm",
|
||||
"value": "ICLSCRepair1-1"
|
||||
}
|
||||
],
|
||||
"status": "entered-in-error",
|
||||
"class": {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "AMB",
|
||||
"display": "Ambulatory"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.ciussscentreouest.ca/codesystem/mode/iclsc",
|
||||
"code": "1",
|
||||
"display": "1 Rencontre présence usager"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"subject": {
|
||||
"reference": "Patient/237643"
|
||||
},
|
||||
"period": {
|
||||
"start": "2020-07-26T13:36:00-04:00",
|
||||
"end": "2020-07-26T14:06:00-04:00"
|
||||
},
|
||||
"length": {
|
||||
"value": 30,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Encounter/242976"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Provenance",
|
||||
"target": [
|
||||
{
|
||||
"reference": "#"
|
||||
}
|
||||
],
|
||||
"recorded": "2024-07-26T14:51:28.222+00:00",
|
||||
"activity": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-DataOperation",
|
||||
"code": "CREATE",
|
||||
"display": "Create"
|
||||
}
|
||||
]
|
||||
},
|
||||
"agent": [
|
||||
{
|
||||
"role": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleClass",
|
||||
"code": "AGNT",
|
||||
"display": "Agent"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"who": {
|
||||
"display": "ICLSC Encounter Repair loop",
|
||||
"reference": "Patient/237643"
|
||||
}
|
||||
}
|
||||
],
|
||||
"entity": [
|
||||
{
|
||||
"role": "source",
|
||||
"what": {
|
||||
"reference": "Encounter/242976/_history/1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Provenance"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "237643",
|
||||
"identifier" : [{
|
||||
"system" : "urn:oid:1.2.36.146.595.217.0.1",
|
||||
"value" : "abc"
|
||||
}],
|
||||
"name": [ {
|
||||
"family": "smith",
|
||||
"given": [ "John" ]
|
||||
} ],
|
||||
"gender": "male",
|
||||
"birthDate": "1988-08-10"
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Patient/237643"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Provenance",
|
||||
"target": [
|
||||
{
|
||||
"reference": "#"
|
||||
}
|
||||
],
|
||||
"recorded": "2024-07-26T14:51:28.222+00:00",
|
||||
"activity": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-DataOperation",
|
||||
"code": "CREATE",
|
||||
"display": "Create"
|
||||
}
|
||||
]
|
||||
},
|
||||
"agent": [
|
||||
{
|
||||
"role": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleClass",
|
||||
"code": "AGNT",
|
||||
"display": "Agent"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"who": {
|
||||
"display": "ICLSC Encounter Repair loop"
|
||||
}
|
||||
}
|
||||
],
|
||||
"entity": [
|
||||
{
|
||||
"role": "source",
|
||||
"what": {
|
||||
"reference": "Encounter/242976/_history/1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Provenance"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "https://smilecdrmock.harrisarc.ca/fhir-system/Encounter/242976",
|
||||
"resource": {
|
||||
"resourceType": "Encounter",
|
||||
"id": "242976",
|
||||
"meta": {
|
||||
"versionId": "4",
|
||||
"lastUpdated": "2024-07-26T10:43:49.287-04:00",
|
||||
"source": "#d9d63c433c828a4d"
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "official",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "VN"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://www.ciussscentreouest.ca/ids/visit-number/iclsc/dlm",
|
||||
"value": "ICLSCRepair1-1"
|
||||
}
|
||||
],
|
||||
"status": "entered-in-error",
|
||||
"class": {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "AMB",
|
||||
"display": "Ambulatory"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.ciussscentreouest.ca/codesystem/mode/iclsc",
|
||||
"code": "1",
|
||||
"display": "1 Rencontre présence usager"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"subject": {
|
||||
"reference": "Patient/237643"
|
||||
},
|
||||
"period": {
|
||||
"start": "2020-07-26T13:36:00-04:00",
|
||||
"end": "2020-07-26T14:06:00-04:00"
|
||||
},
|
||||
"length": {
|
||||
"value": 30,
|
||||
"unit": "min"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Encounter/242976"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Provenance",
|
||||
"target": [
|
||||
{
|
||||
"reference": "#"
|
||||
}
|
||||
],
|
||||
"recorded": "2024-07-26T14:51:28.222+00:00",
|
||||
"activity": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-DataOperation",
|
||||
"code": "CREATE",
|
||||
"display": "Create"
|
||||
}
|
||||
]
|
||||
},
|
||||
"agent": [
|
||||
{
|
||||
"role": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-RoleClass",
|
||||
"code": "AGNT",
|
||||
"display": "Agent"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"who": {
|
||||
"display": "ICLSC Encounter Repair loop"
|
||||
}
|
||||
}
|
||||
],
|
||||
"entity": [
|
||||
{
|
||||
"role": "source",
|
||||
"what": {
|
||||
"reference": "Encounter/242976/_history/1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Provenance"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -727,6 +727,27 @@ public abstract class BaseStorageDao {
|
|||
ourLog.debug(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of references that have versions in their ID whose versions should not be stripped
|
||||
*
|
||||
* @return A set of references that should not have their client-given versions stripped according to the
|
||||
* versioned references settings.
|
||||
*/
|
||||
public static Set<IBaseReference> extractReferencesToAvoidReplacement(
|
||||
FhirContext theFhirContext, IBaseResource theResource) {
|
||||
if (!theFhirContext
|
||||
.getParserOptions()
|
||||
.getDontStripVersionsFromReferencesAtPaths()
|
||||
.isEmpty()) {
|
||||
String theResourceType = theFhirContext.getResourceType(theResource);
|
||||
Set<String> versionReferencesPaths = theFhirContext
|
||||
.getParserOptions()
|
||||
.getDontStripVersionsFromReferencesAtPathsByResourceType(theResourceType);
|
||||
return getReferencesWithOrWithoutVersionId(versionReferencesPaths, theFhirContext, theResource, false);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of references that should be auto-versioned.
|
||||
*
|
||||
|
@ -760,7 +781,7 @@ public abstract class BaseStorageDao {
|
|||
MetaUtil.getAutoVersionReferencesAtPath(theResource.getMeta(), resourceType);
|
||||
|
||||
if (!autoVersionReferencesAtPaths.isEmpty()) {
|
||||
return getReferencesWithoutVersionId(autoVersionReferencesAtPaths, theFhirContext, theResource);
|
||||
return getReferencesWithOrWithoutVersionId(autoVersionReferencesAtPaths, theFhirContext, theResource, true);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
@ -776,17 +797,30 @@ public abstract class BaseStorageDao {
|
|||
String resourceName = theFhirContext.getResourceType(theResource);
|
||||
Set<String> autoVersionReferencesPaths =
|
||||
theStorageSettings.getAutoVersionReferenceAtPathsByResourceType(resourceName);
|
||||
return getReferencesWithoutVersionId(autoVersionReferencesPaths, theFhirContext, theResource);
|
||||
return getReferencesWithOrWithoutVersionId(autoVersionReferencesPaths, theFhirContext, theResource, true);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private static Set<IBaseReference> getReferencesWithoutVersionId(
|
||||
Set<String> autoVersionReferencesPaths, FhirContext theFhirContext, IBaseResource theResource) {
|
||||
return autoVersionReferencesPaths.stream()
|
||||
/**
|
||||
* Extracts references from given resource and filters references by those with versions, or those without versions.
|
||||
*
|
||||
* @param theVersionReferencesPaths the paths from which to extract references from
|
||||
* @param theFhirContext the FHIR context
|
||||
* @param theResource the resource from which to extract references from
|
||||
* @param theShouldFilterByRefsWithoutVersionId If true, this method will return only references without a version. If false, this method will return only references with a version.
|
||||
* @return Set of references contained in the resource with or without versions
|
||||
*/
|
||||
private static Set<IBaseReference> getReferencesWithOrWithoutVersionId(
|
||||
Set<String> theVersionReferencesPaths,
|
||||
FhirContext theFhirContext,
|
||||
IBaseResource theResource,
|
||||
boolean theShouldFilterByRefsWithoutVersionId) {
|
||||
return theVersionReferencesPaths.stream()
|
||||
.map(fullPath -> theFhirContext.newTerser().getValues(theResource, fullPath, IBaseReference.class))
|
||||
.flatMap(Collection::stream)
|
||||
.filter(reference -> !reference.getReferenceElement().hasVersionIdPart())
|
||||
.filter(reference ->
|
||||
reference.getReferenceElement().hasVersionIdPart() ^ theShouldFilterByRefsWithoutVersionId)
|
||||
.collect(Collectors.toMap(ref -> ref, ref -> ref, (oldRef, newRef) -> oldRef, IdentityHashMap::new))
|
||||
.keySet();
|
||||
}
|
||||
|
|
|
@ -274,19 +274,20 @@ public abstract class BaseTransactionProcessor {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleTransactionCreateOrUpdateOutcome(
|
||||
IdSubstitutionMap idSubstitutions,
|
||||
Map<IIdType, DaoMethodOutcome> idToPersistedOutcome,
|
||||
IIdType nextResourceId,
|
||||
DaoMethodOutcome outcome,
|
||||
IBase newEntry,
|
||||
IdSubstitutionMap theIdSubstitutions,
|
||||
Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||
IIdType theNextResourceId,
|
||||
DaoMethodOutcome theOutcome,
|
||||
IBase theNewEntry,
|
||||
String theResourceType,
|
||||
IBaseResource theRes,
|
||||
RequestDetails theRequestDetails) {
|
||||
IIdType newId = outcome.getId().toUnqualified();
|
||||
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
||||
if (newId.equals(resourceId) == false) {
|
||||
if (!nextResourceId.isEmpty()) {
|
||||
idSubstitutions.put(resourceId, newId);
|
||||
IIdType newId = theOutcome.getId().toUnqualified();
|
||||
IIdType resourceId =
|
||||
isPlaceholder(theNextResourceId) ? theNextResourceId : theNextResourceId.toUnqualifiedVersionless();
|
||||
if (!newId.equals(resourceId)) {
|
||||
if (!theNextResourceId.isEmpty()) {
|
||||
theIdSubstitutions.put(resourceId, newId);
|
||||
}
|
||||
if (isPlaceholder(resourceId)) {
|
||||
/*
|
||||
|
@ -294,27 +295,27 @@ public abstract class BaseTransactionProcessor {
|
|||
*/
|
||||
IIdType id = myContext.getVersion().newIdType();
|
||||
id.setValue(theResourceType + '/' + resourceId.getValue());
|
||||
idSubstitutions.put(id, newId);
|
||||
theIdSubstitutions.put(id, newId);
|
||||
}
|
||||
}
|
||||
|
||||
populateIdToPersistedOutcomeMap(idToPersistedOutcome, newId, outcome);
|
||||
populateIdToPersistedOutcomeMap(theIdToPersistedOutcome, newId, theOutcome);
|
||||
|
||||
if (shouldSwapBinaryToActualResource(theRes, theResourceType, nextResourceId)) {
|
||||
theRes = idToPersistedOutcome.get(newId).getResource();
|
||||
if (shouldSwapBinaryToActualResource(theRes, theResourceType, theNextResourceId)) {
|
||||
theRes = theIdToPersistedOutcome.get(newId).getResource();
|
||||
}
|
||||
|
||||
if (outcome.getCreated()) {
|
||||
myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_201_CREATED));
|
||||
if (theOutcome.getCreated()) {
|
||||
myVersionAdapter.setResponseStatus(theNewEntry, toStatusString(Constants.STATUS_HTTP_201_CREATED));
|
||||
} else {
|
||||
myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_200_OK));
|
||||
myVersionAdapter.setResponseStatus(theNewEntry, toStatusString(Constants.STATUS_HTTP_200_OK));
|
||||
}
|
||||
|
||||
Date lastModified = getLastModified(theRes);
|
||||
myVersionAdapter.setResponseLastModified(newEntry, lastModified);
|
||||
myVersionAdapter.setResponseLastModified(theNewEntry, lastModified);
|
||||
|
||||
if (outcome.getOperationOutcome() != null) {
|
||||
myVersionAdapter.setResponseOutcome(newEntry, outcome.getOperationOutcome());
|
||||
if (theOutcome.getOperationOutcome() != null) {
|
||||
myVersionAdapter.setResponseOutcome(theNewEntry, theOutcome.getOperationOutcome());
|
||||
}
|
||||
|
||||
if (theRequestDetails != null) {
|
||||
|
@ -323,9 +324,9 @@ public abstract class BaseTransactionProcessor {
|
|||
RestfulServerUtils.parsePreferHeader(null, prefer).getReturn();
|
||||
if (preferReturn != null) {
|
||||
if (preferReturn == PreferReturnEnum.REPRESENTATION) {
|
||||
if (outcome.getResource() != null) {
|
||||
outcome.fireResourceViewCallbacks();
|
||||
myVersionAdapter.setResource(newEntry, outcome.getResource());
|
||||
if (theOutcome.getResource() != null) {
|
||||
theOutcome.fireResourceViewCallbacks();
|
||||
myVersionAdapter.setResource(theNewEntry, theOutcome.getResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1685,9 +1686,9 @@ public abstract class BaseTransactionProcessor {
|
|||
IdSubstitutionMap theIdSubstitutions,
|
||||
Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||
StopWatch theTransactionStopWatch,
|
||||
EntriesToProcessMap entriesToProcess,
|
||||
Set<IIdType> nonUpdatedEntities,
|
||||
Set<IBasePersistedResource> updatedEntities) {
|
||||
EntriesToProcessMap theEntriesToProcess,
|
||||
Set<IIdType> theNonUpdatedEntities,
|
||||
Set<IBasePersistedResource> theUpdatedEntities) {
|
||||
FhirTerser terser = myContext.newTerser();
|
||||
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
|
||||
IdentityHashMap<DaoMethodOutcome, Set<IBaseReference>> deferredIndexesForAutoVersioning = null;
|
||||
|
@ -1712,6 +1713,9 @@ public abstract class BaseTransactionProcessor {
|
|||
|
||||
Set<IBaseReference> referencesToAutoVersion =
|
||||
BaseStorageDao.extractReferencesToAutoVersion(myContext, myStorageSettings, nextResource);
|
||||
Set<IBaseReference> referencesToKeepClientSuppliedVersion =
|
||||
BaseStorageDao.extractReferencesToAvoidReplacement(myContext, nextResource);
|
||||
|
||||
if (referencesToAutoVersion.isEmpty()) {
|
||||
// no references to autoversion - we can do the resolve and save now
|
||||
resolveReferencesThenSaveAndIndexResource(
|
||||
|
@ -1719,13 +1723,14 @@ public abstract class BaseTransactionProcessor {
|
|||
theTransactionDetails,
|
||||
theIdSubstitutions,
|
||||
theIdToPersistedOutcome,
|
||||
entriesToProcess,
|
||||
nonUpdatedEntities,
|
||||
updatedEntities,
|
||||
theEntriesToProcess,
|
||||
theNonUpdatedEntities,
|
||||
theUpdatedEntities,
|
||||
terser,
|
||||
nextOutcome,
|
||||
nextResource,
|
||||
referencesToAutoVersion); // this is empty
|
||||
referencesToAutoVersion, // this is empty
|
||||
referencesToKeepClientSuppliedVersion);
|
||||
} else {
|
||||
// we have autoversioned things to defer until later
|
||||
if (deferredIndexesForAutoVersioning == null) {
|
||||
|
@ -1742,19 +1747,22 @@ public abstract class BaseTransactionProcessor {
|
|||
DaoMethodOutcome nextOutcome = nextEntry.getKey();
|
||||
Set<IBaseReference> referencesToAutoVersion = nextEntry.getValue();
|
||||
IBaseResource nextResource = nextOutcome.getResource();
|
||||
Set<IBaseReference> referencesToKeepClientSuppliedVersion =
|
||||
BaseStorageDao.extractReferencesToAvoidReplacement(myContext, nextResource);
|
||||
|
||||
resolveReferencesThenSaveAndIndexResource(
|
||||
theRequest,
|
||||
theTransactionDetails,
|
||||
theIdSubstitutions,
|
||||
theIdToPersistedOutcome,
|
||||
entriesToProcess,
|
||||
nonUpdatedEntities,
|
||||
updatedEntities,
|
||||
theEntriesToProcess,
|
||||
theNonUpdatedEntities,
|
||||
theUpdatedEntities,
|
||||
terser,
|
||||
nextOutcome,
|
||||
nextResource,
|
||||
referencesToAutoVersion);
|
||||
referencesToAutoVersion,
|
||||
referencesToKeepClientSuppliedVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1764,15 +1772,16 @@ public abstract class BaseTransactionProcessor {
|
|||
TransactionDetails theTransactionDetails,
|
||||
IdSubstitutionMap theIdSubstitutions,
|
||||
Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||
EntriesToProcessMap entriesToProcess,
|
||||
Set<IIdType> nonUpdatedEntities,
|
||||
Set<IBasePersistedResource> updatedEntities,
|
||||
FhirTerser terser,
|
||||
EntriesToProcessMap theEntriesToProcess,
|
||||
Set<IIdType> theNonUpdatedEntities,
|
||||
Set<IBasePersistedResource> theUpdatedEntities,
|
||||
FhirTerser theTerser,
|
||||
DaoMethodOutcome theDaoMethodOutcome,
|
||||
IBaseResource theResource,
|
||||
Set<IBaseReference> theReferencesToAutoVersion) {
|
||||
Set<IBaseReference> theReferencesToAutoVersion,
|
||||
Set<IBaseReference> theReferencesToKeepClientSuppliedVersion) {
|
||||
// References
|
||||
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(theResource);
|
||||
List<ResourceReferenceInfo> allRefs = theTerser.getAllResourceReferences(theResource);
|
||||
for (ResourceReferenceInfo nextRef : allRefs) {
|
||||
IBaseReference resourceReference = nextRef.getResourceReference();
|
||||
IIdType nextId = resourceReference.getReferenceElement();
|
||||
|
@ -1794,18 +1803,20 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
}
|
||||
if (newId != null || theIdSubstitutions.containsSource(nextId)) {
|
||||
if (shouldReplaceResourceReference(
|
||||
theReferencesToAutoVersion, theReferencesToKeepClientSuppliedVersion, resourceReference)) {
|
||||
if (newId == null) {
|
||||
newId = theIdSubstitutions.getForSource(nextId);
|
||||
}
|
||||
if (newId != null) {
|
||||
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
|
||||
|
||||
if (theReferencesToAutoVersion.contains(resourceReference)) {
|
||||
replaceResourceReference(newId, resourceReference, theTransactionDetails);
|
||||
} else {
|
||||
replaceResourceReference(newId.toVersionless(), resourceReference, theTransactionDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nextId.getValue().startsWith("urn:")) {
|
||||
throw new InvalidRequestException(
|
||||
Msg.code(541) + "Unable to satisfy placeholder ID " + nextId.getValue()
|
||||
|
@ -1858,7 +1869,7 @@ public abstract class BaseTransactionProcessor {
|
|||
// URIs
|
||||
Class<? extends IPrimitiveType<?>> uriType = (Class<? extends IPrimitiveType<?>>)
|
||||
myContext.getElementDefinition("uri").getImplementingClass();
|
||||
List<? extends IPrimitiveType<?>> allUris = terser.getAllPopulatedChildElementsOfType(theResource, uriType);
|
||||
List<? extends IPrimitiveType<?>> allUris = theTerser.getAllPopulatedChildElementsOfType(theResource, uriType);
|
||||
for (IPrimitiveType<?> nextRef : allUris) {
|
||||
if (nextRef instanceof IIdType) {
|
||||
continue; // No substitution on the resource ID itself!
|
||||
|
@ -1886,7 +1897,7 @@ public abstract class BaseTransactionProcessor {
|
|||
IJpaDao jpaDao = (IJpaDao) dao;
|
||||
|
||||
IBasePersistedResource updateOutcome = null;
|
||||
if (updatedEntities.contains(theDaoMethodOutcome.getEntity())) {
|
||||
if (theUpdatedEntities.contains(theDaoMethodOutcome.getEntity())) {
|
||||
boolean forceUpdateVersion = !theReferencesToAutoVersion.isEmpty();
|
||||
String matchUrl = theDaoMethodOutcome.getMatchUrl();
|
||||
RestOperationTypeEnum operationType = theDaoMethodOutcome.getOperationType();
|
||||
|
@ -1903,7 +1914,7 @@ public abstract class BaseTransactionProcessor {
|
|||
theTransactionDetails);
|
||||
updateOutcome = daoMethodOutcome.getEntity();
|
||||
theDaoMethodOutcome = daoMethodOutcome;
|
||||
} else if (!nonUpdatedEntities.contains(theDaoMethodOutcome.getId())) {
|
||||
} else if (!theNonUpdatedEntities.contains(theDaoMethodOutcome.getId())) {
|
||||
updateOutcome = jpaDao.updateEntity(
|
||||
theRequest,
|
||||
theResource,
|
||||
|
@ -1920,7 +1931,7 @@ public abstract class BaseTransactionProcessor {
|
|||
if (updateOutcome != null) {
|
||||
IIdType newId = updateOutcome.getIdDt();
|
||||
|
||||
IIdType entryId = entriesToProcess.getIdWithVersionlessComparison(newId);
|
||||
IIdType entryId = theEntriesToProcess.getIdWithVersionlessComparison(newId);
|
||||
if (entryId != null && !StringUtils.equals(entryId.getValue(), newId.getValue())) {
|
||||
entryId.setValue(newId.getValue());
|
||||
}
|
||||
|
@ -1930,12 +1941,32 @@ public abstract class BaseTransactionProcessor {
|
|||
theIdSubstitutions.updateTargets(newId);
|
||||
|
||||
if (theDaoMethodOutcome.getOperationOutcome() != null) {
|
||||
IBase responseEntry = entriesToProcess.getResponseBundleEntryWithVersionlessComparison(newId);
|
||||
IBase responseEntry = theEntriesToProcess.getResponseBundleEntryWithVersionlessComparison(newId);
|
||||
myVersionAdapter.setResponseOutcome(responseEntry, theDaoMethodOutcome.getOperationOutcome());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We should replace the references when
|
||||
* 1. It is not a reference we should keep the client-supplied version for as configured by `DontStripVersionsFromReferences` or
|
||||
* 2. It is a reference that has been identified for auto versioning or
|
||||
* 3. Is a placeholder reference
|
||||
* @param theReferencesToAutoVersion list of references identified for auto versioning
|
||||
* @param theReferencesToKeepClientSuppliedVersion list of references that we should not strip the version for
|
||||
* @param theResourceReference the resource reference
|
||||
* @return true if we should replace the resource reference, false if we should keep the client provided reference
|
||||
*/
|
||||
private boolean shouldReplaceResourceReference(
|
||||
Set<IBaseReference> theReferencesToAutoVersion,
|
||||
Set<IBaseReference> theReferencesToKeepClientSuppliedVersion,
|
||||
IBaseReference theResourceReference) {
|
||||
return (!theReferencesToKeepClientSuppliedVersion.contains(theResourceReference)
|
||||
&& myContext.getParserOptions().isStripVersionsFromReferences())
|
||||
|| theReferencesToAutoVersion.contains(theResourceReference)
|
||||
|| isPlaceholder(theResourceReference.getReferenceElement());
|
||||
}
|
||||
|
||||
private void replaceResourceReference(
|
||||
IIdType theReferenceId, IBaseReference theResourceReference, TransactionDetails theTransactionDetails) {
|
||||
addRollbackReferenceRestore(theTransactionDetails, theResourceReference);
|
||||
|
|
Loading…
Reference in New Issue