Merge branch 'master' into do-20231213-core-bump-6-2-6
This commit is contained in:
commit
44eb86972e
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
@ -36,6 +37,7 @@ import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
|||
import ca.uhn.fhir.util.bundle.SearchBundleEntryParts;
|
||||
import com.google.common.collect.Sets;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
|
@ -56,6 +58,7 @@ import java.util.Set;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.hl7.fhir.instance.model.api.IBaseBundle.LINK_PREV;
|
||||
|
||||
|
@ -642,6 +645,41 @@ public class BundleUtil {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static IBase getReferenceInBundle(
|
||||
@Nonnull FhirContext theFhirContext, @Nonnull String theUrl, @Nullable Object theAppContext) {
|
||||
if (!(theAppContext instanceof IBaseBundle) || isBlank(theUrl) || theUrl.startsWith("#")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a reference that is a UUID, we must be looking for local references within a Bundle
|
||||
*/
|
||||
IBaseBundle bundle = (IBaseBundle) theAppContext;
|
||||
|
||||
final boolean isPlaceholderReference = theUrl.startsWith("urn:");
|
||||
final String unqualifiedVersionlessReference =
|
||||
new IdDt(theUrl).toUnqualifiedVersionless().getValue();
|
||||
|
||||
for (BundleEntryParts next : BundleUtil.toListOfEntries(theFhirContext, bundle)) {
|
||||
IBaseResource nextResource = next.getResource();
|
||||
if (nextResource == null) {
|
||||
continue;
|
||||
}
|
||||
if (isPlaceholderReference) {
|
||||
if (theUrl.equals(next.getUrl())
|
||||
|| theUrl.equals(nextResource.getIdElement().getValue())) {
|
||||
return nextResource;
|
||||
}
|
||||
} else {
|
||||
if (unqualifiedVersionlessReference.equals(
|
||||
nextResource.getIdElement().toUnqualifiedVersionless().getValue())) {
|
||||
return nextResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
|
||||
* is a patch if the payload is a binary resource containing a patch. This method
|
||||
|
|
|
@ -130,6 +130,8 @@ public enum VersionEnum {
|
|||
|
||||
V6_10_0,
|
||||
V6_10_1,
|
||||
V6_10_2,
|
||||
V6_10_3,
|
||||
V6_11_0,
|
||||
|
||||
V7_0_0;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
release-date: "2023-08-31"
|
||||
release-date: "2023-12-18"
|
||||
codename: "Zed"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
### Major Database Change
|
||||
|
||||
This release fixes a migration from 6.10.1 that was ineffective for SQL Server (MSSQL) instances.
|
||||
This may take several minutes on a larger system (e.g. 10 minutes for 100 million resources).
|
||||
For zero-downtime, or for larger systems, we recommend you upgrade the schema using the CLI tools.
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
release-date: "2023-12-22"
|
||||
codename: "Zed"
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 5465
|
||||
title: "Several fixes to the HAPI FHIR generated OpenAPI schema have been implemented. This means that
|
||||
the spec now validates cleanly. Thanks to Primož Delopst for the contribution!"
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 5564
|
||||
title: "Previously, FHIRPath expression evaluation when using the `_fhirpath` parameter would not work on chained
|
||||
use of 'resolve()'. This was most notable when using `_fhirpath` with FHIR Documents (i.e. 'Bundle' of type 'document'
|
||||
where 'entry[0]' is a 'Composition'). This has now been fixed."
|
|
@ -198,7 +198,11 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
|||
"Column HFJ_SPIDX_STRING.SP_VALUE_NORMALIZED already has a collation of 'C' so doing nothing");
|
||||
}
|
||||
|
||||
version.addTask(new ForceIdMigrationFixTask(version.getRelease(), "20231213.1"));
|
||||
// This fix was bad for MSSQL, it has been set to do nothing.
|
||||
version.addTask(new ForceIdMigrationFixTask(version.getRelease(), "20231213.1").setDoNothing(true));
|
||||
|
||||
// This fix will work for MSSQL or Oracle.
|
||||
version.addTask(new ForceIdMigrationFixTask(version.getRelease(), "20231222.1"));
|
||||
}
|
||||
|
||||
protected void init680() {
|
||||
|
|
|
@ -41,12 +41,10 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.StringUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import ca.uhn.fhir.util.bundle.BundleEntryParts;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Sets;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
@ -59,14 +57,12 @@ import org.apache.commons.text.StringTokenizer;
|
|||
import org.fhir.ucum.Pair;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
|
@ -2004,47 +2000,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final <T extends IBase> T resolveResourceInBundleWithPlaceholderId(Object theAppContext, String theUrl) {
|
||||
/*
|
||||
* If this is a reference that is a UUID, we must be looking for local
|
||||
* references within a Bundle
|
||||
*/
|
||||
if (theAppContext instanceof IBaseBundle && isNotBlank(theUrl) && !theUrl.startsWith("#")) {
|
||||
String unqualifiedVersionlessReference;
|
||||
boolean isPlaceholderReference;
|
||||
if (theUrl.startsWith("urn:")) {
|
||||
isPlaceholderReference = true;
|
||||
unqualifiedVersionlessReference = null;
|
||||
} else {
|
||||
isPlaceholderReference = false;
|
||||
unqualifiedVersionlessReference =
|
||||
new IdType(theUrl).toUnqualifiedVersionless().getValue();
|
||||
}
|
||||
|
||||
List<BundleEntryParts> entries = BundleUtil.toListOfEntries(getContext(), (IBaseBundle) theAppContext);
|
||||
for (BundleEntryParts next : entries) {
|
||||
if (next.getResource() != null) {
|
||||
if (isPlaceholderReference) {
|
||||
if (theUrl.equals(next.getUrl())
|
||||
|| theUrl.equals(
|
||||
next.getResource().getIdElement().getValue())) {
|
||||
return (T) next.getResource();
|
||||
}
|
||||
} else {
|
||||
if (unqualifiedVersionlessReference.equals(next.getResource()
|
||||
.getIdElement()
|
||||
.toUnqualifiedVersionless()
|
||||
.getValue())) {
|
||||
return (T) next.getResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IValueExtractor {
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
|||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.sl.cache.Cache;
|
||||
import ca.uhn.fhir.sl.cache.CacheFactory;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
@ -150,9 +151,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object theAppContext, String theUrl, Base refContext)
|
||||
throws FHIRException {
|
||||
Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
|
||||
public Base resolveReference(FHIRPathEngine engine, Object theAppContext, String theUrl, Base refContext) {
|
||||
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, theAppContext);
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
|||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.sl.cache.Cache;
|
||||
import ca.uhn.fhir.sl.cache.CacheFactory;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
@ -150,9 +151,8 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object theAppContext, String theUrl, Base refContext)
|
||||
throws FHIRException {
|
||||
Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
|
||||
public Base resolveReference(FHIRPathEngine engine, Object theAppContext, String theUrl, Base refContext) {
|
||||
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, theAppContext);
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
|||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.sl.cache.Cache;
|
||||
import ca.uhn.fhir.sl.cache.CacheFactory;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
|
@ -147,9 +148,8 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object appContext, String theUrl, Base refContext)
|
||||
throws FHIRException {
|
||||
Base retVal = resolveResourceInBundleWithPlaceholderId(appContext, theUrl);
|
||||
public Base resolveReference(FHIRPathEngine engine, Object appContext, String theUrl, Base refContext) {
|
||||
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, appContext);
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import ca.uhn.fhir.util.ClasspathUtil;
|
|||
import ca.uhn.fhir.util.ExtensionConstants;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer;
|
||||
import com.vladsch.flexmark.parser.Parser;
|
||||
import io.swagger.v3.core.util.Yaml;
|
||||
|
@ -47,9 +48,13 @@ import io.swagger.v3.oas.models.examples.Example;
|
|||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.media.Content;
|
||||
import io.swagger.v3.oas.models.media.DateSchema;
|
||||
import io.swagger.v3.oas.models.media.DateTimeSchema;
|
||||
import io.swagger.v3.oas.models.media.MediaType;
|
||||
import io.swagger.v3.oas.models.media.NumberSchema;
|
||||
import io.swagger.v3.oas.models.media.ObjectSchema;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.media.StringSchema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
|
@ -73,6 +78,7 @@ import org.hl7.fhir.r4.model.CapabilityStatement;
|
|||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.OperationDefinition;
|
||||
|
@ -109,6 +115,7 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -679,9 +686,10 @@ public class OpenApiInterceptor {
|
|||
operation.addParametersItem(parametersItem);
|
||||
|
||||
parametersItem.setName(nextSearchParam.getName());
|
||||
parametersItem.setRequired(false);
|
||||
parametersItem.setIn("query");
|
||||
parametersItem.setDescription(nextSearchParam.getDocumentation());
|
||||
parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
|
||||
parametersItem.setSchema(toSchema(nextSearchParam.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,7 +773,13 @@ public class OpenApiInterceptor {
|
|||
"/" + theResourceType + "/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.GET);
|
||||
populateOperation(
|
||||
theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, true);
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
theResourceType,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/" + theResourceType + "/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.GET);
|
||||
}
|
||||
if (operationDefinition.getInstance()) {
|
||||
Operation operation = getPathItem(
|
||||
|
@ -774,13 +788,26 @@ public class OpenApiInterceptor {
|
|||
PathItem.HttpMethod.GET);
|
||||
addResourceIdParameter(operation);
|
||||
populateOperation(
|
||||
theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, true);
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
theResourceType,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/" + theResourceType + "/{id}/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.GET);
|
||||
}
|
||||
} else {
|
||||
if (operationDefinition.getSystem()) {
|
||||
Operation operation =
|
||||
getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.GET);
|
||||
populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, true);
|
||||
populateOperation(
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
null,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.GET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -793,7 +820,13 @@ public class OpenApiInterceptor {
|
|||
"/" + theResourceType + "/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.POST);
|
||||
populateOperation(
|
||||
theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
theResourceType,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/" + theResourceType + "/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.POST);
|
||||
}
|
||||
if (operationDefinition.getInstance()) {
|
||||
Operation operation = getPathItem(
|
||||
|
@ -802,13 +835,26 @@ public class OpenApiInterceptor {
|
|||
PathItem.HttpMethod.POST);
|
||||
addResourceIdParameter(operation);
|
||||
populateOperation(
|
||||
theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
theResourceType,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/" + theResourceType + "/{id}/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.POST);
|
||||
}
|
||||
} else {
|
||||
if (operationDefinition.getSystem()) {
|
||||
Operation operation =
|
||||
getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
||||
populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, false);
|
||||
populateOperation(
|
||||
theFhirContext,
|
||||
theOpenApi,
|
||||
null,
|
||||
operationDefinition,
|
||||
operation,
|
||||
"/$" + operationDefinition.getCode(),
|
||||
PathItem.HttpMethod.POST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -847,16 +893,18 @@ public class OpenApiInterceptor {
|
|||
String theResourceType,
|
||||
OperationDefinition theOperationDefinition,
|
||||
Operation theOperation,
|
||||
boolean theGet) {
|
||||
String thePath,
|
||||
PathItem.HttpMethod httpMethod) {
|
||||
if (theResourceType == null) {
|
||||
theOperation.addTagsItem(PAGE_SYSTEM);
|
||||
} else {
|
||||
theOperation.addTagsItem(theResourceType);
|
||||
}
|
||||
theOperation.setSummary(theOperationDefinition.getTitle());
|
||||
theOperation.setSummary(Optional.ofNullable(theOperationDefinition.getTitle())
|
||||
.orElse(String.format("%s: %s", httpMethod.name(), thePath)));
|
||||
theOperation.setDescription(theOperationDefinition.getDescription());
|
||||
addFhirResourceResponse(theFhirContext, theOpenApi, theOperation, null);
|
||||
if (theGet) {
|
||||
if (httpMethod == PathItem.HttpMethod.GET) {
|
||||
|
||||
for (OperationDefinition.OperationDefinitionParameterComponent nextParameter :
|
||||
theOperationDefinition.getParameter()) {
|
||||
|
@ -873,8 +921,8 @@ public class OpenApiInterceptor {
|
|||
parametersItem.setName(nextParameter.getName());
|
||||
parametersItem.setIn("query");
|
||||
parametersItem.setDescription(nextParameter.getDocumentation());
|
||||
parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
|
||||
parametersItem.setRequired(nextParameter.getMin() > 0);
|
||||
parametersItem.setSchema(toSchema(nextParameter.getSearchType()));
|
||||
|
||||
List<Extension> exampleExtensions =
|
||||
nextParameter.getExtensionsByUrl(HapiExtensions.EXT_OP_PARAMETER_EXAMPLE_VALUE);
|
||||
|
@ -1024,6 +1072,7 @@ public class OpenApiInterceptor {
|
|||
parameter.setIn("path");
|
||||
parameter.setDescription("The resource version ID");
|
||||
parameter.setExample("1");
|
||||
parameter.setRequired(true);
|
||||
parameter.setSchema(new Schema().type("string").minimum(new BigDecimal(1)));
|
||||
parameter.setStyle(Parameter.StyleEnum.SIMPLE);
|
||||
theOperation.addParametersItem(parameter);
|
||||
|
@ -1085,6 +1134,7 @@ public class OpenApiInterceptor {
|
|||
parameter.setIn("path");
|
||||
parameter.setDescription("The resource ID");
|
||||
parameter.setExample("123");
|
||||
parameter.setRequired(true);
|
||||
parameter.setSchema(new Schema().type("string").minimum(new BigDecimal(1)));
|
||||
parameter.setStyle(Parameter.StyleEnum.SIMPLE);
|
||||
theOperation.addParametersItem(parameter);
|
||||
|
@ -1188,4 +1238,31 @@ public class OpenApiInterceptor {
|
|||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private Schema<?> toSchema(Enumerations.SearchParamType type) {
|
||||
if (type == null) {
|
||||
return new StringSchema();
|
||||
}
|
||||
switch (type) {
|
||||
case NUMBER:
|
||||
return new NumberSchema();
|
||||
case DATE:
|
||||
Schema<?> dateSchema = new Schema<>();
|
||||
dateSchema.anyOf(ImmutableList.of(new DateTimeSchema(), new DateSchema()));
|
||||
return dateSchema;
|
||||
case QUANTITY:
|
||||
Schema<?> quantitySchema = new Schema<>();
|
||||
quantitySchema.anyOf(ImmutableList.of(new StringSchema(), new NumberSchema()));
|
||||
return quantitySchema;
|
||||
case STRING:
|
||||
case TOKEN:
|
||||
case REFERENCE:
|
||||
case COMPOSITE:
|
||||
case URI:
|
||||
case SPECIAL:
|
||||
case NULL:
|
||||
default:
|
||||
return new StringSchema();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.server.interceptor;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
|
||||
import ca.uhn.fhir.fhirpath.IFhirPath;
|
||||
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
|
@ -29,10 +30,14 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -65,6 +70,12 @@ public class FhirPathFilterInterceptor {
|
|||
ParametersUtil.addPartString(ctx, resultPart, "expression", expression);
|
||||
|
||||
IFhirPath fhirPath = ctx.newFhirPath();
|
||||
fhirPath.setEvaluationContext(new IFhirPathEvaluationContext() {
|
||||
@Override
|
||||
public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
|
||||
return BundleUtil.getReferenceInBundle(ctx, theReference.getValue(), responseResource);
|
||||
}
|
||||
});
|
||||
List<IBase> outputs;
|
||||
try {
|
||||
outputs = fhirPath.evaluate(responseResource, expression, IBase.class);
|
||||
|
|
|
@ -100,7 +100,7 @@ public class ForceIdMigrationFixTask extends BaseTask {
|
|||
+
|
||||
// avoid useless updates on engines that don't check
|
||||
// skip case 1, 2. Only check 3,4,5
|
||||
" where (fhir_id is null or fhir_id <> trim(fhir_id)) "
|
||||
getWhereClauseByDBType()
|
||||
+
|
||||
// chunk range.
|
||||
" and res_id >= ? and res_id < ?",
|
||||
|
@ -109,6 +109,15 @@ public class ForceIdMigrationFixTask extends BaseTask {
|
|||
}
|
||||
}
|
||||
|
||||
private String getWhereClauseByDBType() {
|
||||
switch (getDriverType()) {
|
||||
case MSSQL_2012:
|
||||
return " where (fhir_id is null or DATALENGTH(fhir_id) > LEN(fhir_id)) ";
|
||||
default:
|
||||
return " where (fhir_id is null or fhir_id <> trim(fhir_id)) ";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generateHashCode(HashCodeBuilder theBuilder) {
|
||||
// no-op - this is a singleton.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
|
@ -13,40 +12,63 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.Composition;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.MedicationAdministration;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Period;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FhirPathFilterInterceptorTest {
|
||||
public class FhirPathFilterInterceptorR4Test {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirPathFilterInterceptorTest.class);
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirPathFilterInterceptorR4Test.class);
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
@Order(0)
|
||||
@RegisterExtension
|
||||
public HttpClientExtension myHttpClientExtension = new HttpClientExtension();
|
||||
@Order(0)
|
||||
@RegisterExtension
|
||||
public RestfulServerExtension myServerExtension = new RestfulServerExtension(ourCtx);
|
||||
@Order(1)
|
||||
@RegisterExtension
|
||||
public HashMapResourceProviderExtension<Patient> myProviderExtension = new HashMapResourceProviderExtension<>(myServerExtension, Patient.class);
|
||||
public HashMapResourceProviderExtension<Patient> myPatientProvider = new HashMapResourceProviderExtension<>(myServerExtension, Patient.class);
|
||||
@Order(1)
|
||||
@RegisterExtension
|
||||
public HashMapResourceProviderExtension<MedicationAdministration> myMedicationAdministrationProvider = new HashMapResourceProviderExtension<>(myServerExtension, MedicationAdministration.class);
|
||||
@Order(1)
|
||||
@RegisterExtension
|
||||
public HashMapResourceProviderExtension<Bundle> myBundleProvider = new HashMapResourceProviderExtension<>(myServerExtension, Bundle.class);
|
||||
private IGenericClient myClient;
|
||||
private String myBaseUrl;
|
||||
private CloseableHttpClient myHttpClient;
|
||||
private IIdType myPatientId;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myProviderExtension.clear();
|
||||
myServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
|
||||
myServerExtension.getRestfulServer().getInterceptorService().registerInterceptor(new FhirPathFilterInterceptor());
|
||||
|
||||
|
@ -57,9 +79,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
@Test
|
||||
public void testUnfilteredResponse() throws IOException {
|
||||
createPatient();
|
||||
IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId.getValue());
|
||||
HttpGet request = new HttpGet(patientId.getValue());
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -72,9 +94,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
@Test
|
||||
public void testUnfilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
|
||||
myServerExtension.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId.getValue() + "?_format=" + Constants.FORMATS_HTML_JSON);
|
||||
HttpGet request = new HttpGet(patientId.getValue() + "?_format=" + Constants.FORMATS_HTML_JSON);
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -85,9 +107,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
@Test
|
||||
public void testFilteredResponse() throws IOException {
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_pretty=true");
|
||||
HttpGet request = new HttpGet(patientId + "?_fhirpath=Patient.identifier&_pretty=true");
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -99,9 +121,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
@Test
|
||||
public void testFilteredResponse_ExpressionReturnsExtension() throws IOException {
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.extension('http://hl7.org/fhir/us/core/StructureDefinition/us-core-race')&_pretty=true");
|
||||
HttpGet request = new HttpGet(patientId + "?_fhirpath=Patient.extension('http://hl7.org/fhir/us/core/StructureDefinition/us-core-race')&_pretty=true");
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -112,9 +134,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
@Test
|
||||
public void testFilteredResponse_ExpressionReturnsResource() throws IOException {
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient&_pretty=true");
|
||||
HttpGet request = new HttpGet(patientId + "?_fhirpath=Patient&_pretty=true");
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -127,9 +149,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
@Test
|
||||
public void testFilteredResponse_ExpressionIsInvalid() throws IOException {
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=" + UrlUtil.escapeUrlParam("***"));
|
||||
HttpGet request = new HttpGet(patientId + "?_fhirpath=" + UrlUtil.escapeUrlParam("***"));
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -160,9 +182,9 @@ public class FhirPathFilterInterceptorTest {
|
|||
@Test
|
||||
public void testFilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
|
||||
myServerExtension.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
|
||||
createPatient();
|
||||
final IIdType patientId = createPatient();
|
||||
|
||||
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_format=" + Constants.FORMATS_HTML_JSON);
|
||||
HttpGet request = new HttpGet(patientId + "?_fhirpath=Patient.identifier&_format=" + Constants.FORMATS_HTML_JSON);
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
|
@ -172,19 +194,75 @@ public class FhirPathFilterInterceptorTest {
|
|||
|
||||
}
|
||||
|
||||
private void createPatient() {
|
||||
public static Stream<Arguments> getBundleParameters() {
|
||||
return Stream.of(
|
||||
Arguments.of("Bundle.entry.resource.type", "valueCodeableConcept"),
|
||||
Arguments.of("Bundle.entry.resource.ofType(Patient).identifier", "valueIdentifier"),
|
||||
Arguments.of("Bundle.entry.resource.ofType(MedicationAdministration).effective", "valuePeriod"),
|
||||
Arguments.of("Bundle.entry[0].resource.as(Composition).type", "valueCodeableConcept"),
|
||||
Arguments.of("Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier", "valueIdentifier"),
|
||||
Arguments.of("Bundle.entry[0].resource.as(Composition).section.entry.resolve().as(MedicationAdministration).effective", "valuePeriod")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource(value = "getBundleParameters")
|
||||
public void testFilteredResponse_withBundleComposition_returnsResult(final String theFhirPathExpression, final String expectedResult) throws IOException {
|
||||
IIdType bundle = createBundleDocument();
|
||||
|
||||
HttpGet request = new HttpGet(bundle.getValue() + "?_fhirpath=" + theFhirPathExpression);
|
||||
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
|
||||
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response:\n{}", responseText);
|
||||
IBaseResource resource = ourCtx.newJsonParser().parseResource(responseText);
|
||||
assertTrue(resource instanceof Parameters);
|
||||
Parameters parameters = (Parameters)resource;
|
||||
Parameters.ParametersParameterComponent parameterComponent = parameters.getParameter("result");
|
||||
assertNotNull(parameterComponent);
|
||||
assertEquals(2, parameterComponent.getPart().size());
|
||||
Parameters.ParametersParameterComponent resultComponent = parameterComponent.getPart().get(1);
|
||||
assertEquals("result", resultComponent.getName());
|
||||
assertThat(responseText, containsString(expectedResult));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IIdType createPatient() {
|
||||
Patient p = new Patient();
|
||||
p.addExtension()
|
||||
.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race")
|
||||
.addExtension()
|
||||
.setUrl("ombCategory")
|
||||
.setValue(new Coding("urn:oid:2.16.840.1.113883.6.238", "2106-3", "White"));
|
||||
.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race")
|
||||
.addExtension()
|
||||
.setUrl("ombCategory")
|
||||
.setValue(new Coding("urn:oid:2.16.840.1.113883.6.238", "2106-3", "White"));
|
||||
p.setActive(true);
|
||||
p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1");
|
||||
p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2");
|
||||
p.addName().setFamily("Simpson").addGiven("Homer").addGiven("Jay");
|
||||
p.addName().setFamily("Simpson").addGiven("Grandpa");
|
||||
myPatientId = myClient.create().resource(p).execute().getId().withServerBase(myBaseUrl, "Patient");
|
||||
return myClient.create().resource(p).execute().getId().withServerBase(myBaseUrl, "Patient");
|
||||
}
|
||||
|
||||
private IIdType createBundleDocument() {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("http://identifiers/1").setValue("value-1");
|
||||
patient.addName().setFamily("Simpson").addGiven("Homer").addGiven("Jay");
|
||||
patient = (Patient) myClient.create().resource(patient).execute().getResource();
|
||||
|
||||
MedicationAdministration medicationAdministration = new MedicationAdministration();
|
||||
medicationAdministration.setEffective(new Period().setStartElement(DateTimeType.now()));
|
||||
medicationAdministration = (MedicationAdministration) myClient.create().resource(medicationAdministration).execute().getResource();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||
Composition composition = new Composition();
|
||||
composition.setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("http://example.org")));
|
||||
bundle.addEntry().setResource(composition);
|
||||
composition.getSubject().setReference(patient.getIdElement().getValue());
|
||||
composition.addSection().addEntry(new Reference(medicationAdministration.getIdElement()));
|
||||
bundle.addEntry().setResource(patient);
|
||||
bundle.addEntry().setResource(medicationAdministration);
|
||||
|
||||
return myClient.create().resource(bundle).execute().getId().withServerBase(myBaseUrl, "Bundle");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue