4888 Add validation for composite SearchParameter components types (#4909)
* Added unit tests for composite SearchParameter validator * Added validation for composite SearchParameter components * validation for composite SearchParameter components - fixes * Modified tests * fixed string in setDefinition * validation for composite SearchParameter components - fixed validation and Unit tests * validation for composite SearchParameter components - added method getActiveSearchParameterByComponentDefinition and unit tests * validation for composite SearchParameter components - minor fixes * validation for composite SearchParameter components - remove getActiveSearchParameterByComponentDefinition method * validation for composite SearchParameter components - optimise import * validation for composite SearchParameter components - fix changelog * validation for composite SearchParameter components - improved validation logic * validation for composite SearchParameter components - improved validation logic (remove unused lines) * validation for composite SearchParameter components - improved validation logic * validation for composite SearchParameter components - improved validation logic * validation for composite SearchParameter components - fixed validation logic * validation for composite SearchParameter components - added test for uri and number combo search * validation for composite SearchParameter components - added test for uri and number combo search * validation for composite SearchParameter components - validation logic fix * validation for composite SearchParameter components - fixes * validation for composite SearchParameter components - fixes * validation for composite SearchParameter components - test fixes --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com>
This commit is contained in:
parent
e37edfcf84
commit
e28398fc4c
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 4888
|
||||||
|
jira: SMILE-6047
|
||||||
|
title: "Previously, it was possible to create composite SP with any types of SP as components.
|
||||||
|
This has been fixed by limiting the component SP types to String, Token, Date, or Quantity."
|
|
@ -103,6 +103,10 @@ public class ExtendedHSearchIndexExtractor {
|
||||||
.filter(nextParam -> !nextParam.isMissing())
|
.filter(nextParam -> !nextParam.isMissing())
|
||||||
.forEach(nextParam -> retVal.addQuantityIndexData(nextParam.getParamName(), convertQuantity(nextParam)));
|
.forEach(nextParam -> retVal.addQuantityIndexData(nextParam.getParamName(), convertQuantity(nextParam)));
|
||||||
|
|
||||||
|
theNewParams.myUriParams.stream()
|
||||||
|
.filter(nextParam -> !nextParam.isMissing())
|
||||||
|
.forEach(nextParam -> retVal.addUriIndexData(nextParam.getParamName(), nextParam.getUri()));
|
||||||
|
|
||||||
theResource.getMeta().getTag().forEach(tag ->
|
theResource.getMeta().getTag().forEach(tag ->
|
||||||
retVal.addTokenIndexData("_tag", tag));
|
retVal.addTokenIndexData("_tag", tag));
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,19 @@ public class SearchParamRegistryImplTest {
|
||||||
assertThat(mySearchParamRegistry.getActiveComboSearchParams("Patient"), is(empty()));
|
assertThat(mySearchParamRegistry.getActiveComboSearchParams("Patient"), is(empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetActiveSearchParamByUrl_whenSPExists_returnsActiveSp() {
|
||||||
|
RuntimeSearchParam patientLanguageSp = mySearchParamRegistry.getActiveSearchParamByUrl("SearchParameter/Patient-language");
|
||||||
|
assertNotNull(patientLanguageSp);
|
||||||
|
assertEquals(patientLanguageSp.getId().getIdPart(), "Patient-language");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetActiveSearchParamByUrl_whenSPNotExist_returnsNull() {
|
||||||
|
RuntimeSearchParam nonExistingSp = mySearchParamRegistry.getActiveSearchParamByUrl("SearchParameter/nonExistingSp");
|
||||||
|
assertNull(nonExistingSp);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetActiveSearchParamsRetries() {
|
public void testGetActiveSearchParamsRetries() {
|
||||||
AtomicBoolean retried = new AtomicBoolean(false);
|
AtomicBoolean retried = new AtomicBoolean(false);
|
||||||
|
|
|
@ -19,15 +19,28 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.jpa.search;
|
package ca.uhn.fhir.jpa.search;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
|
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
|
||||||
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import ca.uhn.fhir.util.HapiExtensions;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.BooleanType;
|
||||||
|
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.DecimalType;
|
||||||
|
import org.hl7.fhir.r4.model.Device;
|
||||||
|
import org.hl7.fhir.r4.model.Enumerations;
|
||||||
|
import org.hl7.fhir.r4.model.Extension;
|
||||||
|
import org.hl7.fhir.r4.model.Meta;
|
||||||
|
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||||
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.EnabledIf;
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
@ -167,43 +180,18 @@ public abstract class CompositeSearchParameterTestCases implements ITestDataBuil
|
||||||
@Test
|
@Test
|
||||||
void searchUriNumber_onSameResource_found() {
|
void searchUriNumber_onSameResource_found() {
|
||||||
// Combine existing SPs to test uri + number
|
// Combine existing SPs to test uri + number
|
||||||
createResourceFromJson("""
|
SearchParameter searchParameter = createCompositeSearchParameter("uri-number-compound-test", "RiskAssessment");
|
||||||
{
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/Resource-source", "meta.source"));
|
||||||
"resourceType": "SearchParameter",
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/RiskAssessment-probability", "prediction.probability"));
|
||||||
"name": "uri-number-compound-test",
|
doCreateResource(searchParameter);
|
||||||
"status": "active",
|
|
||||||
"description": "dummy to exercise uri + number",
|
|
||||||
"code": "uri-number-compound-test",
|
|
||||||
"base": [ "RiskAssessment" ],
|
|
||||||
"type": "composite",
|
|
||||||
"expression": "RiskAssessment",
|
|
||||||
"component": [ {
|
|
||||||
"definition": "http://hl7.org/fhir/SearchParameter/Resource-source",
|
|
||||||
"expression": "meta.source"
|
|
||||||
}, {
|
|
||||||
"definition": "http://hl7.org/fhir/SearchParameter/RiskAssessment-probability",
|
|
||||||
"expression": "prediction.probability"
|
|
||||||
} ]
|
|
||||||
}""");
|
|
||||||
// enable this sp.
|
// enable this sp.
|
||||||
myTestDaoSearch.getSearchParamRegistry().forceRefresh();
|
myTestDaoSearch.getSearchParamRegistry().forceRefresh();
|
||||||
|
|
||||||
IIdType raId = createResourceFromJson("""
|
RiskAssessment riskAssessment = new RiskAssessment();
|
||||||
{
|
riskAssessment.setMeta(new Meta().setSource("https://example.com/ourSource"));
|
||||||
"resourceType": "RiskAssessment",
|
riskAssessment.addPrediction(new RiskAssessment.RiskAssessmentPredictionComponent().setProbability(new DecimalType(0.02)));
|
||||||
"meta": {
|
IIdType raId = doCreateResource(riskAssessment);
|
||||||
"source": "https://example.com/ourSource"
|
|
||||||
},
|
|
||||||
"prediction": [
|
|
||||||
{
|
|
||||||
"outcome": {
|
|
||||||
"text": "Heart Attack"
|
|
||||||
},
|
|
||||||
"probabilityDecimal": 0.02
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
// verify config
|
// verify config
|
||||||
myTestDaoSearch.assertSearchFinds("simple uri search works", "RiskAssessment?_source=https://example.com/ourSource", raId);
|
myTestDaoSearch.assertSearchFinds("simple uri search works", "RiskAssessment?_source=https://example.com/ourSource", raId);
|
||||||
|
@ -212,6 +200,91 @@ public abstract class CompositeSearchParameterTestCases implements ITestDataBuil
|
||||||
myTestDaoSearch.assertSearchFinds("composite uri + number", "RiskAssessment?uri-number-compound-test=https://example.com/ourSource$0.02", raId);
|
myTestDaoSearch.assertSearchFinds("composite uri + number", "RiskAssessment?uri-number-compound-test=https://example.com/ourSource$0.02", raId);
|
||||||
myTestDaoSearch.assertSearchNotFound("both params must match ", "RiskAssessment?uri-number-compound-test=https://example.com/ourSource$0.08", raId);
|
myTestDaoSearch.assertSearchNotFound("both params must match ", "RiskAssessment?uri-number-compound-test=https://example.com/ourSource$0.08", raId);
|
||||||
myTestDaoSearch.assertSearchNotFound("both params must match ", "RiskAssessment?uri-number-compound-test=https://example.com/otherUrI$0.02", raId);
|
myTestDaoSearch.assertSearchNotFound("both params must match ", "RiskAssessment?uri-number-compound-test=https://example.com/otherUrI$0.02", raId);
|
||||||
|
//verify combo query
|
||||||
|
myTestDaoSearch.assertSearchFinds("combo uri + number", "RiskAssessment?_source=https://example.com/ourSource&probability=0.02", raId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("extensionProvider")
|
||||||
|
void testComboSearch_withTokenAndNumber_returnsMatchingResources(Extension theExtension) {
|
||||||
|
// Combine existing SPs to test Token + number
|
||||||
|
SearchParameter searchParameter = createCompositeSearchParameter("token-number-combo-test", "RiskAssessment");
|
||||||
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/RiskAssessment-method", "RiskAssessment"));
|
||||||
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/RiskAssessment-probability", "RiskAssessment"));
|
||||||
|
searchParameter.setExtension(List.of(theExtension));
|
||||||
|
doCreateResource(searchParameter);
|
||||||
|
|
||||||
|
// enable this sp.
|
||||||
|
myTestDaoSearch.getSearchParamRegistry().forceRefresh();
|
||||||
|
|
||||||
|
RiskAssessment riskAssessment = new RiskAssessment();
|
||||||
|
riskAssessment.setMethod(new CodeableConcept(new Coding(null, "BRCAPRO", null)));
|
||||||
|
riskAssessment.addPrediction(new RiskAssessment.RiskAssessmentPredictionComponent().setProbability(new DecimalType(0.02)));
|
||||||
|
IIdType raId = doCreateResource(riskAssessment);
|
||||||
|
|
||||||
|
RiskAssessment riskAssessmentNonMatch = new RiskAssessment();
|
||||||
|
riskAssessmentNonMatch.setMethod(new CodeableConcept(new Coding(null, "NOT_FOUND_CODE", null)));
|
||||||
|
riskAssessmentNonMatch.addPrediction(new RiskAssessment.RiskAssessmentPredictionComponent().setProbability(new DecimalType(0.03)));
|
||||||
|
doCreateResource(riskAssessmentNonMatch);
|
||||||
|
|
||||||
|
// verify combo query
|
||||||
|
myTestDaoSearch.assertSearchFinds("combo uri + number", "RiskAssessment?method=BRCAPRO&probability=0.02", raId);
|
||||||
|
myTestDaoSearch.assertSearchNotFound("both params must match", "RiskAssessment?method=CODE&probability=0.02", raId);
|
||||||
|
myTestDaoSearch.assertSearchNotFound("both params must match", "RiskAssessment?method=BRCAPRO&probability=0.09", raId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("extensionProvider")
|
||||||
|
void testComboSearch_withUriAndString_returnsMatchingResources(Extension theExtension) {
|
||||||
|
//Combine existing SPs to test URI + String
|
||||||
|
SearchParameter searchParameter = createCompositeSearchParameter("uri-string-combo-test", "Device");
|
||||||
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/Device-url", "Device"));
|
||||||
|
searchParameter.addComponent(componentFrom("http://hl7.org/fhir/SearchParameter/Device-model", "Device"));
|
||||||
|
searchParameter.setExtension(List.of(theExtension));
|
||||||
|
doCreateResource(searchParameter);
|
||||||
|
|
||||||
|
// enable this sp.
|
||||||
|
myTestDaoSearch.getSearchParamRegistry().forceRefresh();
|
||||||
|
|
||||||
|
Device device = new Device();
|
||||||
|
device.setUrl("http://deviceUrl");
|
||||||
|
device.setModelNumber("modelNumber");
|
||||||
|
|
||||||
|
IIdType deviceId = doCreateResource(device);
|
||||||
|
|
||||||
|
Device deviceNonMatch = new Device();
|
||||||
|
deviceNonMatch.setUrl("http://someurl");
|
||||||
|
deviceNonMatch.setModelNumber("someModelNumber");
|
||||||
|
|
||||||
|
// verify combo query
|
||||||
|
myTestDaoSearch.assertSearchFinds("combo uri + string", "Device?url=http://deviceUrl&model=modelNumber", deviceId);
|
||||||
|
myTestDaoSearch.assertSearchNotFound("both params must match", "Device?url=http://wrongUrl&model=modelNumber", deviceId);
|
||||||
|
myTestDaoSearch.assertSearchNotFound("both params must match", "Device?url=http://deviceUrl&model=wrongModel", deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SearchParameter createCompositeSearchParameter(String theCodeValue, String theBase) {
|
||||||
|
SearchParameter retVal = new SearchParameter();
|
||||||
|
retVal.setId(theCodeValue);
|
||||||
|
retVal.setUrl("http://example.org/" + theCodeValue);
|
||||||
|
retVal.addBase(theBase);
|
||||||
|
retVal.setCode(theCodeValue);
|
||||||
|
retVal.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
retVal.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
retVal.setExpression(theBase);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchParameter.SearchParameterComponentComponent componentFrom(String theDefinition, String theExpression) {
|
||||||
|
return new SearchParameter.SearchParameterComponentComponent().setDefinition(theDefinition).setExpression(theExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> extensionProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(false))), // composite SP of type combo with non-unique index
|
||||||
|
Arguments.of(
|
||||||
|
new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(true))) // composite SP of type combo with non-unique index
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,53 @@
|
||||||
package ca.uhn.fhir.jpa.dao.validation;
|
package ca.uhn.fhir.jpa.dao.validation;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.registry.SearchParameterCanonicalizer;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||||
|
import ca.uhn.fhir.util.HapiExtensions;
|
||||||
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
|
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
|
||||||
import org.hl7.fhir.r4.model.Enumerations;
|
import org.hl7.fhir.r5.model.BooleanType;
|
||||||
import org.hl7.fhir.r4.model.SearchParameter;
|
import org.hl7.fhir.r5.model.Enumerations;
|
||||||
|
import org.hl7.fhir.r5.model.Extension;
|
||||||
|
import org.hl7.fhir.r5.model.SearchParameter;
|
||||||
|
import org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent;
|
||||||
|
import org.hl7.fhir.r5.model.StringType;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Spy;
|
import org.mockito.Spy;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.PublicationStatus.ACTIVE;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.COMPOSITE;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.DATE;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.NUMBER;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.QUANTITY;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.REFERENCE;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.STRING;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.TOKEN;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.SearchParamType.URI;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.VersionIndependentResourceTypesAll.OBSERVATION;
|
||||||
|
import static org.hl7.fhir.r5.model.Enumerations.VersionIndependentResourceTypesAll.PATIENT;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class SearchParameterDaoValidatorTest {
|
public class SearchParameterDaoValidatorTest {
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
private FhirContext ourCtx = FhirContext.forR4Cached();
|
private FhirContext myFhirContext = FhirContext.forR5Cached();
|
||||||
@Mock
|
@Mock
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
@Spy
|
@Spy
|
||||||
|
@ -27,21 +55,48 @@ public class SearchParameterDaoValidatorTest {
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private SearchParameterDaoValidator mySvc;
|
private SearchParameterDaoValidator mySvc;
|
||||||
|
|
||||||
private VersionCanonicalizer myVersionCanonicalizer = new VersionCanonicalizer(ourCtx);
|
private final VersionCanonicalizer myVersionCanonicalizer = new VersionCanonicalizer(myFhirContext);
|
||||||
|
|
||||||
|
private final SearchParameterCanonicalizer mySearchParameterCanonicalizer = new SearchParameterCanonicalizer(myFhirContext);
|
||||||
|
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_TOKEN = "SearchParameter/observation-code";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_REFERENCE = "SearchParameter/observation-patient";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_STRING = "SearchParameter/observation-markdown";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_DATE = "SearchParameter/observation-date";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_QUANTITY = "SearchParameter/observation-code";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_URI = "SearchParameter/component-value-canonical";
|
||||||
|
private static final String SP_COMPONENT_DEFINITION_OF_TYPE_NUMBER = "SearchParameter/component-value-number";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() {
|
||||||
|
createAndMockSearchParameter(TOKEN, SP_COMPONENT_DEFINITION_OF_TYPE_TOKEN, "observation-code", "Observation.code");
|
||||||
|
createAndMockSearchParameter(REFERENCE, SP_COMPONENT_DEFINITION_OF_TYPE_REFERENCE, "observation-patient", "Observation.subject.where(resolve() is Patient");
|
||||||
|
createAndMockSearchParameter(STRING, SP_COMPONENT_DEFINITION_OF_TYPE_DATE, "observation-category", "Observation.value.ofType(markdown)");
|
||||||
|
createAndMockSearchParameter(DATE, SP_COMPONENT_DEFINITION_OF_TYPE_STRING, "observation-date", "Observation.value.ofType(dateTime)");
|
||||||
|
createAndMockSearchParameter(QUANTITY, SP_COMPONENT_DEFINITION_OF_TYPE_QUANTITY, "observation-quantity", "Observation.value.ofType(Quantity)");
|
||||||
|
createAndMockSearchParameter(URI, SP_COMPONENT_DEFINITION_OF_TYPE_URI, "observation-component-value-canonical", "Observation.component.value.ofType(canonical)");
|
||||||
|
createAndMockSearchParameter(NUMBER, SP_COMPONENT_DEFINITION_OF_TYPE_NUMBER, "observation-component-value-number", "Observation.component.valueInteger");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAndMockSearchParameter(Enumerations.SearchParamType theType, String theDefinition, String theCodeValue, String theExpression) {
|
||||||
|
SearchParameter observationCodeSp = createSearchParameter(theType, theDefinition, theCodeValue, theExpression);
|
||||||
|
RuntimeSearchParam observationCodeRuntimeSearchParam = mySearchParameterCanonicalizer.canonicalizeSearchParameter(observationCodeSp);
|
||||||
|
lenient().when(mySearchParamRegistry.getActiveSearchParamByUrl(eq(theDefinition))).thenReturn(observationCodeRuntimeSearchParam);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateSubscription() {
|
public void testValidateSubscription() {
|
||||||
SearchParameter sp = new SearchParameter();
|
SearchParameter sp = new SearchParameter();
|
||||||
sp.setId("SearchParameter/patient-eyecolour");
|
sp.setId("SearchParameter/patient-eyecolour");
|
||||||
sp.setUrl("http://example.org/SearchParameter/patient-eyecolour");
|
sp.setUrl("http://example.org/SearchParameter/patient-eyecolour");
|
||||||
sp.addBase("Patient");
|
sp.addBase(PATIENT);
|
||||||
sp.setCode("eyecolour");
|
sp.setCode("eyecolour");
|
||||||
sp.setType(Enumerations.SearchParamType.TOKEN);
|
sp.setType(TOKEN);
|
||||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
sp.setStatus(ACTIVE);
|
||||||
sp.setExpression("Patient.extension('http://foo')");
|
sp.setExpression("Patient.extension('http://foo')");
|
||||||
sp.addTarget("Patient");
|
sp.addTarget(PATIENT);
|
||||||
|
|
||||||
org.hl7.fhir.r5.model.SearchParameter canonicalSp = myVersionCanonicalizer.searchParameterToCanonical(sp);
|
SearchParameter canonicalSp = myVersionCanonicalizer.searchParameterToCanonical(sp);
|
||||||
mySvc.validate(canonicalSp);
|
mySvc.validate(canonicalSp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,15 +105,130 @@ public class SearchParameterDaoValidatorTest {
|
||||||
SearchParameter sp = new SearchParameter();
|
SearchParameter sp = new SearchParameter();
|
||||||
sp.setId("SearchParameter/meal-chef");
|
sp.setId("SearchParameter/meal-chef");
|
||||||
sp.setUrl("http://example.org/SearchParameter/meal-chef");
|
sp.setUrl("http://example.org/SearchParameter/meal-chef");
|
||||||
sp.addBase("Meal");
|
sp.addExtension(new Extension(HapiExtensions.EXTENSION_SEARCHPARAM_CUSTOM_BASE_RESOURCE).setValue(new StringType("Meal")));
|
||||||
|
sp.addExtension(new Extension(HapiExtensions.EXTENSION_SEARCHPARAM_CUSTOM_TARGET_RESOURCE).setValue(new StringType("Chef")));
|
||||||
sp.setCode("chef");
|
sp.setCode("chef");
|
||||||
sp.setType(Enumerations.SearchParamType.REFERENCE);
|
sp.setType(REFERENCE);
|
||||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
sp.setStatus(ACTIVE);
|
||||||
sp.setExpression("Meal.chef");
|
sp.setExpression("Meal.chef");
|
||||||
sp.addTarget("Chef");
|
|
||||||
|
|
||||||
org.hl7.fhir.r5.model.SearchParameter canonicalSp = myVersionCanonicalizer.searchParameterToCanonical(sp);
|
SearchParameter canonicalSp = myVersionCanonicalizer.searchParameterToCanonical(sp);
|
||||||
mySvc.validate(canonicalSp);
|
mySvc.validate(canonicalSp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("extensionProvider")
|
||||||
|
public void testMethodValidate_nonUniqueComboAndCompositeSearchParamWithComponentOfTypeReference_isNotAllowed(Extension theExtension) {
|
||||||
|
SearchParameter sp = createSearchParameter(COMPOSITE, "SearchParameter/patient-code", "patient-code", "Observation");
|
||||||
|
sp.addExtension(theExtension);
|
||||||
|
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent().setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_TOKEN));
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent().setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_REFERENCE));
|
||||||
|
|
||||||
|
try {
|
||||||
|
mySvc.validate(sp);
|
||||||
|
fail();
|
||||||
|
} catch (UnprocessableEntityException ex) {
|
||||||
|
assertTrue(ex.getMessage().startsWith("HAPI-2347: "));
|
||||||
|
assertTrue(ex.getMessage().contains("Invalid component search parameter type: REFERENCE in component.definition: http://example.org/SearchParameter/observation-patient"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodValidate_uniqueComboSearchParamWithComponentOfTypeReference_isValid() {
|
||||||
|
SearchParameter sp = createSearchParameter(COMPOSITE, "SearchParameter/patient-code", "patient-code", "Observation");
|
||||||
|
sp.addExtension(new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(true)));
|
||||||
|
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_TOKEN));
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_REFERENCE));
|
||||||
|
|
||||||
|
mySvc.validate(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("comboSpProvider")
|
||||||
|
public void testMethodValidate_comboSearchParamsWithNumberUriComponents_isValid(SearchParameter theSearchParameter) {
|
||||||
|
theSearchParameter.addComponent(new SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_URI));
|
||||||
|
theSearchParameter.addComponent(new SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_NUMBER));
|
||||||
|
|
||||||
|
mySvc.validate(theSearchParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodValidate_compositeSearchParamsWithNumberUriComponents_isNotAllowed() {
|
||||||
|
SearchParameter sp = createSearchParameter(COMPOSITE, "SearchParameter/component-value-uri-number", "component-value-uri-number", "Observation");
|
||||||
|
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent().setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_URI));
|
||||||
|
sp.addComponent(new SearchParameterComponentComponent().setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_NUMBER));
|
||||||
|
|
||||||
|
try {
|
||||||
|
mySvc.validate(sp);
|
||||||
|
fail();
|
||||||
|
} catch (UnprocessableEntityException ex) {
|
||||||
|
assertTrue(ex.getMessage().startsWith("HAPI-2347: "));
|
||||||
|
assertTrue(ex.getMessage().contains("Invalid component search parameter type: URI in component.definition: http://example.org/SearchParameter/component-value-canonical"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("compositeSpProvider")
|
||||||
|
// we're testing for:
|
||||||
|
// SP of type composite,
|
||||||
|
// SP of type combo composite non-unique,
|
||||||
|
// SP of type combo composite unique,
|
||||||
|
public void testMethodValidate_allCompositeSpTypesWithComponentOfValidType_isValid(SearchParameter theSearchParameter) {
|
||||||
|
|
||||||
|
theSearchParameter.addComponent(new SearchParameter.SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_TOKEN).setExpression("Observation"));
|
||||||
|
theSearchParameter.addComponent(new SearchParameter.SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_QUANTITY).setExpression("Observation"));
|
||||||
|
theSearchParameter.addComponent(new SearchParameter.SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_STRING).setExpression("Observation"));
|
||||||
|
theSearchParameter.addComponent(new SearchParameter.SearchParameterComponentComponent()
|
||||||
|
.setDefinition(SP_COMPONENT_DEFINITION_OF_TYPE_DATE).setExpression("Observation"));
|
||||||
|
|
||||||
|
mySvc.validate(theSearchParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SearchParameter createSearchParameter(Enumerations.SearchParamType theType, String theId, String theCodeValue, String theExpression) {
|
||||||
|
|
||||||
|
SearchParameter retVal = new SearchParameter();
|
||||||
|
retVal.setId(theId);
|
||||||
|
retVal.setUrl("http://example.org/" + theId);
|
||||||
|
retVal.addBase(OBSERVATION);
|
||||||
|
retVal.setCode(theCodeValue);
|
||||||
|
retVal.setType(theType);
|
||||||
|
retVal.setStatus(ACTIVE);
|
||||||
|
retVal.setExpression(theExpression);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> extensionProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(false))), // composite SP of type combo with non-unique index
|
||||||
|
Arguments.of((Object) null) // composite SP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> comboSpProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(createSearchParameter(Enumerations.SearchParamType.COMPOSITE, "SearchParameter/any-type", "any-type", "Observation")
|
||||||
|
.addExtension(new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(false)))), // composite SP of type combo with non-unique index
|
||||||
|
|
||||||
|
Arguments.of(createSearchParameter(Enumerations.SearchParamType.COMPOSITE, "SearchParameter/any-type", "any-type", "Observation")
|
||||||
|
.addExtension(new Extension(HapiExtensions.EXT_SP_UNIQUE, new BooleanType(true)))) // composite SP of type combo with unique index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> compositeSpProvider() {
|
||||||
|
return Stream.concat(comboSpProvider(), Stream.of(
|
||||||
|
Arguments.of(createSearchParameter(Enumerations.SearchParamType.COMPOSITE, "SearchParameter/any-type", "any-type", "Observation")) // composite SP
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.util.ElementUtil;
|
import ca.uhn.fhir.util.ElementUtil;
|
||||||
|
@ -32,8 +33,19 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.hl7.fhir.r5.model.Enumerations;
|
import org.hl7.fhir.r5.model.Enumerations;
|
||||||
import org.hl7.fhir.r5.model.SearchParameter;
|
import org.hl7.fhir.r5.model.SearchParameter;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.DATE;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.NUMBER;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.QUANTITY;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.REFERENCE;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.STRING;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.TOKEN;
|
||||||
|
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.URI;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
public class SearchParameterDaoValidator {
|
public class SearchParameterDaoValidator {
|
||||||
|
@ -84,8 +96,6 @@ public class SearchParameterDaoValidator {
|
||||||
throw new UnprocessableEntityException(Msg.code(1113) + "SearchParameter.base is missing");
|
throw new UnprocessableEntityException(Msg.code(1113) + "SearchParameter.base is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isUnique = hasAnyExtensionUniqueSetTo(searchParameter, true);
|
|
||||||
|
|
||||||
if (isCompositeWithoutExpression(searchParameter)) {
|
if (isCompositeWithoutExpression(searchParameter)) {
|
||||||
|
|
||||||
// this is ok
|
// this is ok
|
||||||
|
@ -96,47 +106,58 @@ public class SearchParameterDaoValidator {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (isUnique) {
|
|
||||||
if (searchParameter.getComponent().size() == 0) {
|
|
||||||
throw new UnprocessableEntityException(Msg.code(1115) + "SearchParameter is marked as unique but has no components");
|
|
||||||
}
|
|
||||||
for (SearchParameter.SearchParameterComponentComponent next : searchParameter.getComponent()) {
|
|
||||||
if (isBlank(next.getDefinition())) {
|
|
||||||
throw new UnprocessableEntityException(Msg.code(1116) + "SearchParameter is marked as unique but is missing component.definition");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FhirVersionEnum fhirVersion = myFhirContext.getVersion().getVersion();
|
FhirVersionEnum fhirVersion = myFhirContext.getVersion().getVersion();
|
||||||
if (fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
|
if (fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||||
// omitting validation for DSTU2_HL7ORG, DSTU2_1 and DSTU2
|
// omitting validation for DSTU2_HL7ORG, DSTU2_1 and DSTU2
|
||||||
} else {
|
} else {
|
||||||
|
maybeValidateCompositeSpForUniqueIndexing(searchParameter);
|
||||||
if (myStorageSettings.isValidateSearchParameterExpressionsOnSave()) {
|
maybeValidateSearchParameterExpressionsOnSave(searchParameter);
|
||||||
|
maybeValidateCompositeWithComponent(searchParameter);
|
||||||
validateExpressionPath(searchParameter);
|
|
||||||
|
|
||||||
String expression = getExpression(searchParameter);
|
|
||||||
|
|
||||||
try {
|
|
||||||
myFhirContext.newFhirPath().parse(expression);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new UnprocessableEntityException(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"" + expression + "\": " + exception.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isCompositeSp(SearchParameter theSearchParameter) {
|
||||||
|
return theSearchParameter.getType() != null && theSearchParameter.getType().equals(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isCompositeWithoutBase(SearchParameter searchParameter) {
|
private boolean isCompositeWithoutBase(SearchParameter searchParameter) {
|
||||||
return
|
return
|
||||||
ElementUtil.isEmpty(searchParameter.getBase()) &&
|
ElementUtil.isEmpty(searchParameter.getBase()) &&
|
||||||
ElementUtil.isEmpty(searchParameter.getExtensionsByUrl(HapiExtensions.EXTENSION_SEARCHPARAM_CUSTOM_BASE_RESOURCE)) &&
|
ElementUtil.isEmpty(searchParameter.getExtensionsByUrl(HapiExtensions.EXTENSION_SEARCHPARAM_CUSTOM_BASE_RESOURCE)) &&
|
||||||
(searchParameter.getType() == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(searchParameter.getType().name()));
|
!isCompositeSp(searchParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCompositeWithoutExpression(SearchParameter searchParameter) {
|
private boolean isCompositeWithoutExpression(SearchParameter searchParameter) {
|
||||||
return searchParameter.getType() != null && searchParameter.getType().name().equals(Enumerations.SearchParamType.COMPOSITE.name()) && isBlank(searchParameter.getExpression());
|
return isCompositeSp(searchParameter) && isBlank(searchParameter.getExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCompositeWithComponent(SearchParameter theSearchParameter) {
|
||||||
|
return isCompositeSp(theSearchParameter) && theSearchParameter.hasComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCompositeSpForUniqueIndexing(SearchParameter theSearchParameter) {
|
||||||
|
return isCompositeSp(theSearchParameter) && hasAnyExtensionUniqueSetTo(theSearchParameter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeValidateCompositeSpForUniqueIndexing(SearchParameter theSearchParameter) {
|
||||||
|
if (isCompositeSpForUniqueIndexing(theSearchParameter)) {
|
||||||
|
if (!theSearchParameter.hasComponent()) {
|
||||||
|
throw new UnprocessableEntityException(Msg.code(1115) + "SearchParameter is marked as unique but has no components");
|
||||||
|
}
|
||||||
|
for (SearchParameter.SearchParameterComponentComponent next : theSearchParameter.getComponent()) {
|
||||||
|
if (isBlank(next.getDefinition())) {
|
||||||
|
throw new UnprocessableEntityException(Msg.code(1116) + "SearchParameter is marked as unique but is missing component.definition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeValidateSearchParameterExpressionsOnSave(SearchParameter theSearchParameter) {
|
||||||
|
if (myStorageSettings.isValidateSearchParameterExpressionsOnSave()) {
|
||||||
|
validateExpressionPath(theSearchParameter);
|
||||||
|
validateExpressionIsParsable(theSearchParameter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateExpressionPath(SearchParameter theSearchParameter) {
|
private void validateExpressionPath(SearchParameter theSearchParameter) {
|
||||||
|
@ -153,6 +174,16 @@ public class SearchParameterDaoValidator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateExpressionIsParsable(SearchParameter theSearchParameter) {
|
||||||
|
String expression = getExpression(theSearchParameter);
|
||||||
|
|
||||||
|
try {
|
||||||
|
myFhirContext.newFhirPath().parse(expression);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new UnprocessableEntityException(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"" + expression + "\": " + exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getExpression(SearchParameter theSearchParameter) {
|
private String getExpression(SearchParameter theSearchParameter) {
|
||||||
return theSearchParameter.getExpression().trim();
|
return theSearchParameter.getExpression().trim();
|
||||||
}
|
}
|
||||||
|
@ -165,4 +196,56 @@ public class SearchParameterDaoValidator {
|
||||||
.stream()
|
.stream()
|
||||||
.anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
|
.anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeValidateCompositeWithComponent(SearchParameter theSearchParameter) {
|
||||||
|
if (isCompositeWithComponent(theSearchParameter)) {
|
||||||
|
validateCompositeSearchParameterComponents(theSearchParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateCompositeSearchParameterComponents(SearchParameter theSearchParameter) {
|
||||||
|
theSearchParameter.getComponent().stream()
|
||||||
|
.filter(SearchParameter.SearchParameterComponentComponent::hasDefinition)
|
||||||
|
.map(SearchParameter.SearchParameterComponentComponent::getDefinition)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(mySearchParamRegistry::getActiveSearchParamByUrl)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(theRuntimeSp -> validateComponentSpTypeAgainstWhiteList(theRuntimeSp, getAllowedSearchParameterTypes(theSearchParameter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateComponentSpTypeAgainstWhiteList(RuntimeSearchParam theRuntimeSearchParam,
|
||||||
|
Collection<RestSearchParameterTypeEnum> theAllowedSearchParamTypes) {
|
||||||
|
if (!theAllowedSearchParamTypes.contains(theRuntimeSearchParam.getParamType())) {
|
||||||
|
throw new UnprocessableEntityException(String.format("%sInvalid component search parameter type: %s in component.definition: %s, supported types: %s",
|
||||||
|
Msg.code(2347), theRuntimeSearchParam.getParamType().name(), theRuntimeSearchParam.getUri(),
|
||||||
|
theAllowedSearchParamTypes.stream().map(Enum::name).collect(Collectors.joining(", "))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns allowed Search Parameter Types for a given composite or combo search parameter
|
||||||
|
* This prevents the creation of search parameters that would fail during runtime (during a GET request)
|
||||||
|
* Below you can find references to runtime usage for each parameter type:
|
||||||
|
*
|
||||||
|
* For Composite Search Parameters without HSearch indexing enabled (JPA only):
|
||||||
|
* @see QueryStack#createPredicateCompositePart() and SearchBuilder#createCompositeSort()
|
||||||
|
*
|
||||||
|
* For Composite Search Parameters with HSearch indexing enabled:
|
||||||
|
* @see HSearchCompositeSearchIndexDataImpl#writeIndexEntry()
|
||||||
|
*
|
||||||
|
* For Combo Search Parameters:
|
||||||
|
* @see BaseSearchParamExtractor.extractParameterCombinationsForComboParam()
|
||||||
|
*/
|
||||||
|
private Set<RestSearchParameterTypeEnum> getAllowedSearchParameterTypes(SearchParameter theSearchParameter) {
|
||||||
|
// combo unique search parameter
|
||||||
|
if (hasAnyExtensionUniqueSetTo(theSearchParameter, true)) {
|
||||||
|
return Set.of(STRING, TOKEN, DATE, QUANTITY, URI, NUMBER, REFERENCE);
|
||||||
|
// combo non-unique search parameter or composite Search Parameter with HSearch indexing
|
||||||
|
} else if (hasAnyExtensionUniqueSetTo(theSearchParameter, false) || // combo non-unique search parameter
|
||||||
|
myStorageSettings.isAdvancedHSearchIndexing()) { // composite Search Parameter with HSearch indexing
|
||||||
|
return Set.of(STRING, TOKEN, DATE, QUANTITY, URI, NUMBER);
|
||||||
|
} else { // composite Search Parameter (JPA only)
|
||||||
|
return Set.of(STRING, TOKEN, DATE, QUANTITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue