New extension for auto versioning references of the resource (#5591)
New extension for auto-versioning references of the resource
This commit is contained in:
parent
763894c28f
commit
5286829585
|
@ -43,6 +43,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
@ -217,7 +218,8 @@ public abstract class BaseParser implements IParser {
|
|||
});
|
||||
}
|
||||
|
||||
private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
|
||||
private String determineReferenceText(
|
||||
IBaseReference theRef, CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
|
||||
IIdType ref = theRef.getReferenceElement();
|
||||
if (isBlank(ref.getIdPart())) {
|
||||
String reference = ref.getValue();
|
||||
|
@ -241,7 +243,7 @@ public abstract class BaseParser implements IParser {
|
|||
.getResourceDefinition(theRef.getResource())
|
||||
.getName());
|
||||
}
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement)) {
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
|
||||
reference = refId.toVersionless().getValue();
|
||||
} else {
|
||||
reference = refId.getValue();
|
||||
|
@ -258,12 +260,12 @@ public abstract class BaseParser implements IParser {
|
|||
myContext.getResourceDefinition(theRef.getResource()).getName());
|
||||
}
|
||||
if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement)) {
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
|
||||
return ref.toUnqualifiedVersionless().getValue();
|
||||
}
|
||||
return ref.toUnqualified().getValue();
|
||||
}
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement)) {
|
||||
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
|
||||
return ref.toVersionless().getValue();
|
||||
}
|
||||
return ref.getValue();
|
||||
|
@ -604,7 +606,17 @@ public abstract class BaseParser implements IParser {
|
|||
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
|
||||
}
|
||||
|
||||
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
|
||||
private boolean isStripVersionsFromReferences(
|
||||
CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
|
||||
|
||||
Set<String> autoVersionReferencesAtPathExtensions =
|
||||
MetaUtil.getAutoVersionReferencesAtPath(theResource.getMeta(), myContext.getResourceType(theResource));
|
||||
|
||||
if (!autoVersionReferencesAtPathExtensions.isEmpty()
|
||||
&& theCompositeChildElement.anyPathMatches(autoVersionReferencesAtPathExtensions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
|
||||
if (stripVersionsFromReferences != null) {
|
||||
return stripVersionsFromReferences;
|
||||
|
@ -811,7 +823,7 @@ public abstract class BaseParser implements IParser {
|
|||
*/
|
||||
if (next instanceof IBaseReference) {
|
||||
IBaseReference nextRef = (IBaseReference) next;
|
||||
String refText = determineReferenceText(nextRef, theCompositeChildElement);
|
||||
String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource);
|
||||
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
|
||||
|
||||
if (retVal == theValues) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseExtension;
|
|||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -177,15 +178,18 @@ public class ExtensionUtil {
|
|||
* pulls out any extensions that have the given theExtensionUrl and a primitive value type,
|
||||
* and returns a list of the string version of the extension values.
|
||||
*/
|
||||
public static List<String> getExtensionPrimitiveValues(IBaseHasExtensions theBase, String theExtensionUrl) {
|
||||
List<String> values = theBase.getExtension().stream()
|
||||
.filter(t -> theExtensionUrl.equals(t.getUrl()))
|
||||
.filter(t -> t.getValue() instanceof IPrimitiveType<?>)
|
||||
.map(t -> (IPrimitiveType<?>) t.getValue())
|
||||
.map(IPrimitiveType::getValueAsString)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
return values;
|
||||
public static List<String> getExtensionPrimitiveValues(IBase theBase, String theExtensionUrl) {
|
||||
if (theBase instanceof IBaseHasExtensions) {
|
||||
return ((IBaseHasExtensions) theBase)
|
||||
.getExtension().stream()
|
||||
.filter(t -> theExtensionUrl.equals(t.getUrl()))
|
||||
.filter(t -> t.getValue() instanceof IPrimitiveType<?>)
|
||||
.map(t -> (IPrimitiveType<?>) t.getValue())
|
||||
.map(IPrimitiveType::getValueAsString)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -162,6 +162,16 @@ public class HapiExtensions {
|
|||
*/
|
||||
public static final String EXTENSION_SEARCHPARAM_UPLIFT_REFCHAIN =
|
||||
"https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-refchain";
|
||||
|
||||
/**
|
||||
* This extension is used to enable auto version references at path for resource instances.
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Resource.meta</code> element.
|
||||
* It is allowed to add multiple extensions with different paths.
|
||||
*/
|
||||
public static final String EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH =
|
||||
"http://hapifhir.io/fhir/StructureDefinition/auto-version-references-at-path";
|
||||
|
||||
/**
|
||||
* This extension is used for "uplifted refchains" on search parameters. See the
|
||||
* HAPI FHIR documentation for an explanation of how these work.
|
||||
|
|
|
@ -35,6 +35,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -144,4 +146,12 @@ public class MetaUtil {
|
|||
}
|
||||
sourceElement.setValueAsString(theValue);
|
||||
}
|
||||
|
||||
public static Set<String> getAutoVersionReferencesAtPath(IBaseMetaType theMeta, String theResourceType) {
|
||||
return ExtensionUtil.getExtensionPrimitiveValues(
|
||||
theMeta, HapiExtensions.EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH)
|
||||
.stream()
|
||||
.map(path -> String.format("%s.%s", theResourceType, path))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 5588
|
||||
title: "Added `auto-version-references-at-path` extension that allows to
|
||||
enable auto versioning references at specified paths of resource instances."
|
|
@ -166,3 +166,22 @@ You can also configure HAPI to not strip versions only on certain fields. This i
|
|||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|disableStripVersionsField}}
|
||||
```
|
||||
|
||||
# Automatically Versioned References
|
||||
|
||||
It is possible to configure HAPI to automatically version references for desired resource instances by providing the `auto-version-references-at-path` extension in the `Resource.meta` element:
|
||||
|
||||
```json
|
||||
"meta": {
|
||||
"extension":[
|
||||
{
|
||||
"url":"http://hapifhir.io/fhir/StructureDefinition/auto-version-references-at-path",
|
||||
"valueString":"focus"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
It is allowed to add multiple extensions with different paths. When a resource is stored, any references found at the specified paths will have the current version of the target appended, if a version is not already present.
|
||||
|
||||
Parser will not strip versions from references at paths provided by the `auto-version-references-at-path` extension.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -157,6 +157,7 @@ import org.hl7.fhir.r4.model.Media;
|
|||
import org.hl7.fhir.r4.model.Medication;
|
||||
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||
import org.hl7.fhir.r4.model.MedicationRequest;
|
||||
import org.hl7.fhir.r4.model.MessageHeader;
|
||||
import org.hl7.fhir.r4.model.Meta;
|
||||
import org.hl7.fhir.r4.model.MolecularSequence;
|
||||
import org.hl7.fhir.r4.model.NamingSystem;
|
||||
|
@ -433,6 +434,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
@Qualifier("myExplanationOfBenefitDaoR4")
|
||||
protected IFhirResourceDao<ExplanationOfBenefit> myExplanationOfBenefitDao;
|
||||
@Autowired
|
||||
@Qualifier("myMessageHeaderDaoR4")
|
||||
protected IFhirResourceDao<MessageHeader> myMessageHeaderDao;
|
||||
@Autowired
|
||||
protected IResourceTableDao myResourceTableDao;
|
||||
@Autowired
|
||||
protected IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
|
|
|
@ -59,7 +59,9 @@ import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
|||
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.IMetaTagSorter;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
|
@ -86,6 +88,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -690,29 +694,67 @@ public abstract class BaseStorageDao {
|
|||
}
|
||||
|
||||
/**
|
||||
* @see StorageSettings#getAutoVersionReferenceAtPaths()
|
||||
* Extracts a list of references that should be auto-versioned.
|
||||
*
|
||||
* @return A set of references that should be versioned according to both storage settings
|
||||
* and auto-version reference extensions, or it may also be empty.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<IBaseReference> extractReferencesToAutoVersion(
|
||||
FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) {
|
||||
Map<IBaseReference, Object> references = Collections.emptyMap();
|
||||
Set<IBaseReference> referencesToAutoVersionFromConfig =
|
||||
getReferencesToAutoVersionFromConfig(theFhirContext, theStorageSettings, theResource);
|
||||
|
||||
Set<IBaseReference> referencesToAutoVersionFromExtensions =
|
||||
getReferencesToAutoVersionFromExtension(theFhirContext, theResource);
|
||||
|
||||
return Stream.concat(referencesToAutoVersionFromConfig.stream(), referencesToAutoVersionFromExtensions.stream())
|
||||
.collect(Collectors.toMap(ref -> ref, ref -> ref, (oldRef, newRef) -> oldRef, IdentityHashMap::new))
|
||||
.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of references that should be auto-versioned according to
|
||||
* <code>auto-version-references-at-path</code> extensions.
|
||||
* @see HapiExtensions#EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH
|
||||
*/
|
||||
@Nonnull
|
||||
private static Set<IBaseReference> getReferencesToAutoVersionFromExtension(
|
||||
FhirContext theFhirContext, IBaseResource theResource) {
|
||||
String resourceType = theFhirContext.getResourceType(theResource);
|
||||
Set<String> autoVersionReferencesAtPaths =
|
||||
MetaUtil.getAutoVersionReferencesAtPath(theResource.getMeta(), resourceType);
|
||||
|
||||
if (!autoVersionReferencesAtPaths.isEmpty()) {
|
||||
return getReferencesWithoutVersionId(autoVersionReferencesAtPaths, theFhirContext, theResource);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of references that should be auto-versioned according to storage configuration.
|
||||
* @see StorageSettings#getAutoVersionReferenceAtPaths()
|
||||
*/
|
||||
@Nonnull
|
||||
private static Set<IBaseReference> getReferencesToAutoVersionFromConfig(
|
||||
FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) {
|
||||
if (!theStorageSettings.getAutoVersionReferenceAtPaths().isEmpty()) {
|
||||
String resourceName = theFhirContext.getResourceType(theResource);
|
||||
for (String nextPath : theStorageSettings.getAutoVersionReferenceAtPathsByResourceType(resourceName)) {
|
||||
List<IBaseReference> nextReferences =
|
||||
theFhirContext.newTerser().getValues(theResource, nextPath, IBaseReference.class);
|
||||
for (IBaseReference next : nextReferences) {
|
||||
if (next.getReferenceElement().hasVersionIdPart()) {
|
||||
continue;
|
||||
}
|
||||
if (references.isEmpty()) {
|
||||
references = new IdentityHashMap<>();
|
||||
}
|
||||
references.put(next, null);
|
||||
}
|
||||
}
|
||||
Set<String> autoVersionReferencesPaths =
|
||||
theStorageSettings.getAutoVersionReferenceAtPathsByResourceType(resourceName);
|
||||
return getReferencesWithoutVersionId(autoVersionReferencesPaths, theFhirContext, theResource);
|
||||
}
|
||||
return references.keySet();
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private static Set<IBaseReference> getReferencesWithoutVersionId(
|
||||
Set<String> autoVersionReferencesPaths, FhirContext theFhirContext, IBaseResource theResource) {
|
||||
return autoVersionReferencesPaths.stream()
|
||||
.map(fullPath -> theFhirContext.newTerser().getValues(theResource, fullPath, IBaseReference.class))
|
||||
.flatMap(Collection::stream)
|
||||
.filter(reference -> !reference.getReferenceElement().hasVersionIdPart())
|
||||
.collect(Collectors.toMap(ref -> ref, ref -> ref, (oldRef, newRef) -> oldRef, IdentityHashMap::new))
|
||||
.keySet();
|
||||
}
|
||||
|
||||
public static void clearRequestAsProcessingSubRequest(RequestDetails theRequestDetails) {
|
||||
|
|
|
@ -58,6 +58,7 @@ import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
|||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts;
|
||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
|
@ -1680,13 +1681,10 @@ public abstract class BaseTransactionProcessor {
|
|||
if (newId != null) {
|
||||
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
|
||||
|
||||
addRollbackReferenceRestore(theTransactionDetails, resourceReference);
|
||||
if (theReferencesToAutoVersion.contains(resourceReference)) {
|
||||
resourceReference.setReference(newId.getValue());
|
||||
resourceReference.setResource(null);
|
||||
replaceResourceReference(newId, resourceReference, theTransactionDetails);
|
||||
} else {
|
||||
resourceReference.setReference(newId.toVersionless().getValue());
|
||||
resourceReference.setResource(null);
|
||||
replaceResourceReference(newId.toVersionless(), resourceReference, theTransactionDetails);
|
||||
}
|
||||
}
|
||||
} else if (nextId.getValue().startsWith("urn:")) {
|
||||
|
@ -1724,9 +1722,15 @@ public abstract class BaseTransactionProcessor {
|
|||
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
|
||||
|
||||
if (outcome != null && !outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
|
||||
addRollbackReferenceRestore(theTransactionDetails, resourceReference);
|
||||
resourceReference.setReference(nextId.getValue());
|
||||
resourceReference.setResource(null);
|
||||
replaceResourceReference(nextId, resourceReference, theTransactionDetails);
|
||||
}
|
||||
|
||||
// if referenced resource is not in transaction but exists in the DB, resolving its version
|
||||
IResourcePersistentId persistedReferenceId = resourceVersionMap.getResourcePersistentId(nextId);
|
||||
if (outcome == null && persistedReferenceId != null && persistedReferenceId.getVersion() != null) {
|
||||
IIdType newReferenceId = nextId.withVersion(
|
||||
persistedReferenceId.getVersion().toString());
|
||||
replaceResourceReference(newReferenceId, resourceReference, theTransactionDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1814,6 +1818,13 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private void replaceResourceReference(
|
||||
IIdType theReferenceId, IBaseReference theResourceReference, TransactionDetails theTransactionDetails) {
|
||||
addRollbackReferenceRestore(theTransactionDetails, theResourceReference);
|
||||
theResourceReference.setReference(theReferenceId.getValue());
|
||||
theResourceReference.setResource(null);
|
||||
}
|
||||
|
||||
private void addRollbackReferenceRestore(
|
||||
TransactionDetails theTransactionDetails, IBaseReference resourceReference) {
|
||||
String existingValue = resourceReference.getReferenceElement().getValue();
|
||||
|
|
Loading…
Reference in New Issue