Refactor Common SearchParameter Functionality (#4321)

* refactor checkpoint

* clean up

* add method to find runtime search param by id

* new search param cache tests

* extract SearchParameter validation functionality

* create common interface for combo searchparameters

* Remove ResourceTable from extractor interface

* clean up

* move SearchParameterDaoValidator

* resolve code review comments

* remove reflection

* change log

* bump version to 6.3.4-SNAPSHOT

Co-authored-by: nathaniel.doef <nathaniel.doef@smilecdr.com>
This commit is contained in:
Nathan Doef 2022-12-15 06:46:45 -05:00 committed by GitHub
parent 29ebb950e8
commit 7b86eda8a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 718 additions and 331 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,14 +4,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,9 @@
---
type: change
issue: 4363
title: "Common `SearchParameter` functionality has been refactored. In particular,
1) Validation logic performed inside `JpaResourceDaoSearchParameter` has been moved into a new class called
`SearchParameterDaoValidator` 2) Logic for extracting combo unique and combo non-unique search parameters
that was inside `SearchParamWithInlineReferencesExtractor` has been moved into `BaseSearchParamExtractor`
and 3) a new interface called `IResourceIndexComboSearchParameter` has been added which provides a common interface
for combo unique and combo non-unique search parameters."

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -134,6 +134,7 @@ import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.dao.IMdmLinkImplFactory;
@ -145,6 +146,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationInterce
import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc;
import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport;
import org.hl7.fhir.r4.model.Consent;
@ -775,6 +777,11 @@ public class JpaConfig {
return new VersionCanonicalizer(theFhirContext);
}
@Bean
public SearchParameterDaoValidator searchParameterDaoValidator(FhirContext theFhirContext, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) {
return new SearchParameterDaoValidator(theFhirContext, theDaoConfig, theSearchParamRegistry);
}
@Bean
public ITermReadSvc terminologyService() {
return new TermReadSvcImpl();

View File

@ -1,31 +1,18 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -48,10 +35,12 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class JpaResourceDaoSearchParameter<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoSearchParameter<T> {
private static final Pattern REGEX_SP_EXPRESSION_HAS_PATH = Pattern.compile("[( ]*([A-Z][a-zA-Z]+\\.)?[a-z].*");
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Autowired
private SearchParameterDaoValidator mySearchParameterDaoValidator;
protected void reindexAffectedResources(T theResource, RequestDetails theRequestDetails) {
// N.B. Don't do this on the canonicalized version
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
@ -84,88 +73,12 @@ public class JpaResourceDaoSearchParameter<T extends IBaseResource> extends Base
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
validateSearchParam(theResource, getContext(), getConfig());
validateSearchParam(theResource);
}
public void validateSearchParam(IBaseResource theResource, FhirContext theContext, DaoConfig theDaoConfig) {
public void validateSearchParam(IBaseResource theResource) {
org.hl7.fhir.r5.model.SearchParameter searchParameter = myVersionCanonicalizer.searchParameterToCanonical(theResource);
/*
* If overriding built-in SPs is disabled on this server, make sure we aren't
* doing that
*/
if (theDaoConfig.getModelConfig().isDefaultSearchParamsCanBeOverridden() == false) {
for (IPrimitiveType<?> nextBaseType : searchParameter.getBase()) {
String nextBase = nextBaseType.getValueAsString();
RuntimeSearchParam existingSearchParam = mySearchParamRegistry.getActiveSearchParam(nextBase, searchParameter.getCode());
if (existingSearchParam != null) {
boolean isBuiltIn = existingSearchParam.getId() == null;
isBuiltIn |= existingSearchParam.getUri().startsWith("http://hl7.org/fhir/SearchParameter/");
if (isBuiltIn) {
throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + searchParameter.getCode() + " because overriding is disabled on this server");
}
}
}
}
/*
* Everything below is validating that the SP is actually valid. We'll only do that if the
* SPO is active, so that we don't block people from uploading works-in-progress
*/
if (searchParameter.getStatus() == null) {
throw new UnprocessableEntityException(Msg.code(1112) + "SearchParameter.status is missing or invalid");
}
if (!searchParameter.getStatus().name().equals("ACTIVE")) {
return;
}
if (ElementUtil.isEmpty(searchParameter.getBase()) && (searchParameter.getType() == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(searchParameter.getType().name()))) {
throw new UnprocessableEntityException(Msg.code(1113) + "SearchParameter.base is missing");
}
boolean isUnique = hasAnyExtensionUniqueSetTo(searchParameter, true);
if (searchParameter.getType() != null && searchParameter.getType().name().equals(Enumerations.SearchParamType.COMPOSITE.name()) && isBlank(searchParameter.getExpression())) {
// this is ok
} else if (isBlank(searchParameter.getExpression())) {
throw new UnprocessableEntityException(Msg.code(1114) + "SearchParameter.expression is missing");
} 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 = theContext.getVersion().getVersion();
if (fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
// omitting validation for DSTU2_HL7ORG, DSTU2_1 and DSTU2
} else {
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
validateExpressionPath(searchParameter);
String expression = getExpression(searchParameter);
try {
theContext.newFhirPath().parse(expression);
} catch (Exception exception) {
throw new UnprocessableEntityException(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"" + expression + "\": " + exception.getMessage());
}
}
}
}
mySearchParameterDaoValidator.validate(searchParameter);
}
@VisibleForTesting
@ -173,32 +86,8 @@ public class JpaResourceDaoSearchParameter<T extends IBaseResource> extends Base
myVersionCanonicalizer = theVersionCanonicalizer;
}
private static void validateExpressionPath(SearchParameter theSearchParameter) {
String expression = getExpression(theSearchParameter);
boolean isResourceOfTypeComposite = theSearchParameter.getType() == Enumerations.SearchParamType.COMPOSITE;
boolean isResourceOfTypeSpecial = theSearchParameter.getType() == Enumerations.SearchParamType.SPECIAL;
boolean expressionHasPath = REGEX_SP_EXPRESSION_HAS_PATH.matcher(expression).matches();
boolean isUnique = hasAnyExtensionUniqueSetTo(theSearchParameter, true);
if (!isUnique && !isResourceOfTypeComposite && !isResourceOfTypeSpecial && !expressionHasPath) {
throw new UnprocessableEntityException(Msg.code(1120) + "SearchParameter.expression value \"" + expression + "\" is invalid due to missing/incorrect path");
}
@VisibleForTesting
public void setSearchParameterDaoValidatorForUnitTest(SearchParameterDaoValidator theSearchParameterDaoValidator){
mySearchParameterDaoValidator = theSearchParameterDaoValidator;
}
private static String getExpression(SearchParameter theSearchParameter) {
return theSearchParameter.getExpression().trim();
}
private static boolean hasAnyExtensionUniqueSetTo(SearchParameter theSearchParameter, boolean theValue) {
String theValueAsString = Boolean.toString(theValue);
return theSearchParameter
.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE)
.stream()
.anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
}
}

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.dao.index;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
@ -37,15 +36,11 @@ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -54,8 +49,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -64,23 +57,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
@Lazy
public class SearchParamWithInlineReferencesExtractor {
@ -146,124 +133,12 @@ public class SearchParamWithInlineReferencesExtractor {
}
}
/*
* Handle combo parameters
*/
extractCompositeStringUniques(theEntity, theParams);
extractComboParameters(theEntity, theParams);
}
private void extractCompositeStringUniques(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
final String resourceType = theEntity.getResourceType();
List<RuntimeSearchParam> comboSearchParams = mySearchParamRegistry.getActiveComboSearchParams(resourceType);
for (RuntimeSearchParam next : comboSearchParams) {
switch (Objects.requireNonNull(next.getComboSearchParamType())) {
case UNIQUE:
extractComboUniqueParam(theEntity, theParams, resourceType, next);
break;
case NON_UNIQUE:
extractComboNonUniqueParam(theEntity, theParams, resourceType, next);
}
}
}
private void extractComboNonUniqueParam(ResourceTable theEntity, ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myComboTokenNonUnique.add(new ResourceIndexedComboTokenNonUnique(myPartitionSettings, theEntity, nextQueryString));
}
}
private void extractComboUniqueParam(ResourceTable theEntity, ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myComboStringUniques.add(new ResourceIndexedComboStringUnique(theEntity, nextQueryString, theParam.getId()));
}
}
@Nonnull
private Set<String> extractParameterCombinationsForComboParam(ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
List<List<String>> partsChoices = new ArrayList<>();
List<RuntimeSearchParam> compositeComponents = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam);
for (RuntimeSearchParam nextCompositeOf : compositeComponents) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = findParameterIndexes(theParams, nextCompositeOf);
Collection<ResourceLink> linksForCompositePart = null;
switch (nextCompositeOf.getParamType()) {
case REFERENCE:
linksForCompositePart = theParams.myLinks;
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
case URI:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case REFERENCE:
linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit());
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
case URI:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
ArrayList<String> nextChoicesList = new ArrayList<>();
partsChoices.add(nextChoicesList);
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
String value = nextParamAsClientParam.getValueAsQueryToken(myContext);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceType, key);
if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE && param != null && param.getParamType() == RestSearchParameterTypeEnum.STRING) {
value = StringUtil.normalizeStringForSearchIndexing(value);
}
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
assert isNotBlank(nextLink.getTargetResourceType());
assert isNotBlank(nextLink.getTargetResourceId());
String value = nextLink.getTargetResourceType() + "/" + nextLink.getTargetResourceId();
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
}
return ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(theResourceType, partsChoices);
private void extractComboParameters(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
mySearchParamExtractorService.extractSearchParamComboUnique(theEntity, theParams);
mySearchParamExtractorService.extractSearchParamComboNonUnique(theEntity, theParams);
}
@Nullable

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,21 @@
package ca.uhn.fhir.jpa.model.entity;
import org.hl7.fhir.instance.model.api.IIdType;
/**
* Provides a common interface used to extract Combo Unique ({@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique})
* and Combo Non-Unique ({@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique}) SearchParameters
*/
public interface IResourceIndexComboSearchParameter {
IIdType getSearchParameterId();
void setSearchParameterId(IIdType theSearchParameterId);
String getIndexString();
ResourceTable getResource();
void setResource(ResourceTable theResourceTable);
}

View File

@ -35,7 +35,7 @@ import javax.persistence.*;
@Index(name = ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_STRING, columnList = "IDX_STRING", unique = true),
@Index(name = ResourceIndexedComboStringUnique.IDX_IDXCMPSTRUNIQ_RESOURCE, columnList = "RES_ID", unique = false)
})
public class ResourceIndexedComboStringUnique extends BasePartitionable implements Comparable<ResourceIndexedComboStringUnique> {
public class ResourceIndexedComboStringUnique extends BasePartitionable implements Comparable<ResourceIndexedComboStringUnique>, IResourceIndexComboSearchParameter {
public static final int MAX_STRING_LENGTH = 500;
public static final String IDX_IDXCMPSTRUNIQ_STRING = "IDX_IDXCMPSTRUNIQ_STRING";
@ -102,6 +102,7 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable implemen
.isEquals();
}
@Override
public String getIndexString() {
return myIndexString;
}
@ -139,6 +140,7 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable implemen
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
@Override
public void setSearchParameterId(IIdType theSearchParameterId) {
mySearchParameterId = theSearchParameterId;
}
@ -146,6 +148,7 @@ public class ResourceIndexedComboStringUnique extends BasePartitionable implemen
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
@Override
public IIdType getSearchParameterId() {
return mySearchParameterId;
}

View File

@ -27,6 +27,7 @@ import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -48,7 +49,7 @@ import static ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam.hash;
@Index(name = "IDX_IDXCMBTOKNU_STR", columnList = "IDX_STRING", unique = false),
@Index(name = "IDX_IDXCMBTOKNU_RES", columnList = "RES_ID", unique = false)
})
public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implements Comparable<ResourceIndexedComboTokenNonUnique> {
public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implements Comparable<ResourceIndexedComboTokenNonUnique>, IResourceIndexComboSearchParameter {
@SequenceGenerator(name = "SEQ_IDXCMBTOKNU_ID", sequenceName = "SEQ_IDXCMBTOKNU_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_IDXCMBTOKNU_ID")
@ -72,6 +73,9 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
@Transient
private transient PartitionSettings myPartitionSettings;
@Transient
private IIdType mySearchParameterId;
/**
* Constructor
*/
@ -86,6 +90,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
calculateHashes();
}
@Override
public String getIndexString() {
return myIndexString;
}
@ -156,10 +161,15 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
return myPartitionSettings;
}
public void setPartitionSettings(PartitionSettings thePartitionSettings) {
myPartitionSettings = thePartitionSettings;
}
public ResourceTable getResource() {
return myResource;
}
@Override
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
@ -198,4 +208,19 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
return hash(partitionSettings, partitionId, queryString);
}
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
@Override
public void setSearchParameterId(IIdType theSearchParameterId) {
mySearchParameterId = theSearchParameterId;
}
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
@Override
public IIdType getSearchParameterId() {
return mySearchParameterId;
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -32,6 +33,8 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@ -40,8 +43,11 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
@ -50,6 +56,7 @@ import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
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 com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.ObjectUtils;
@ -69,6 +76,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.measure.quantity.Quantity;
import javax.measure.unit.NonSI;
@ -386,6 +394,176 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedComboStringUnique> extractSearchParamComboUnique(String theResourceType, ResourceIndexedSearchParams theParams){
SearchParamSet<ResourceIndexedComboStringUnique> retVal = new SearchParamSet<>();
List<RuntimeSearchParam> runtimeComboUniqueParams = mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.UNIQUE);
for (RuntimeSearchParam runtimeParam : runtimeComboUniqueParams) {
Set<ResourceIndexedComboStringUnique> comboUniqueParams = createComboUniqueParam(theResourceType, theParams, runtimeParam);
retVal.addAll(comboUniqueParams);
}
return retVal;
}
private SearchParamSet<ResourceIndexedComboStringUnique> createComboUniqueParam(String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) {
SearchParamSet<ResourceIndexedComboStringUnique> retVal = new SearchParamSet<>();
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theRuntimeParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
ResourceIndexedComboStringUnique uniqueParam = new ResourceIndexedComboStringUnique();
uniqueParam.setIndexString(nextQueryString);
uniqueParam.setSearchParameterId(theRuntimeParam.getId());
retVal.add(uniqueParam);
}
return retVal;
}
@Override
public SearchParamSet<ResourceIndexedComboTokenNonUnique> extractSearchParamComboNonUnique(String theResourceType, ResourceIndexedSearchParams theParams){
SearchParamSet<ResourceIndexedComboTokenNonUnique> retVal = new SearchParamSet<>();
List<RuntimeSearchParam> runtimeComboNonUniqueParams = mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.NON_UNIQUE);
for (RuntimeSearchParam runtimeParam : runtimeComboNonUniqueParams) {
Set<ResourceIndexedComboTokenNonUnique> comboNonUniqueParams = createComboNonUniqueParam(theResourceType, theParams, runtimeParam);
retVal.addAll(comboNonUniqueParams);
}
return retVal;
}
private SearchParamSet<ResourceIndexedComboTokenNonUnique> createComboNonUniqueParam(String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) {
SearchParamSet<ResourceIndexedComboTokenNonUnique> retVal = new SearchParamSet<>();
Set<String> queryStringsToPopulate = extractParameterCombinationsForComboParam(theParams, theResourceType, theRuntimeParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString);
ResourceIndexedComboTokenNonUnique nonUniqueParam = new ResourceIndexedComboTokenNonUnique();
nonUniqueParam.setPartitionSettings(myPartitionSettings);
nonUniqueParam.setIndexString(nextQueryString);
nonUniqueParam.setSearchParameterId(theRuntimeParam.getId());
retVal.add(nonUniqueParam);
}
return retVal;
}
@Nonnull
private Set<String> extractParameterCombinationsForComboParam(ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
List<List<String>> partsChoices = new ArrayList<>();
List<RuntimeSearchParam> compositeComponents = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam);
for (RuntimeSearchParam nextCompositeOf : compositeComponents) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = findParameterIndexes(theParams, nextCompositeOf);
Collection<ResourceLink> linksForCompositePart = null;
switch (nextCompositeOf.getParamType()) {
case REFERENCE:
linksForCompositePart = theParams.myLinks;
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
case URI:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) {
case REFERENCE:
linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit());
break;
case NUMBER:
case DATE:
case STRING:
case TOKEN:
case QUANTITY:
case URI:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
ArrayList<String> nextChoicesList = new ArrayList<>();
partsChoices.add(nextChoicesList);
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
String value = nextParamAsClientParam.getValueAsQueryToken(myContext);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceType, key);
if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE && param != null && param.getParamType() == RestSearchParameterTypeEnum.STRING) {
value = StringUtil.normalizeStringForSearchIndexing(value);
}
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
assert isNotBlank(nextLink.getTargetResourceType());
assert isNotBlank(nextLink.getTargetResourceId());
String value = nextLink.getTargetResourceType() + "/" + nextLink.getTargetResourceId();
if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value);
}
}
}
}
}
return ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(theResourceType, partsChoices);
}
@Nullable
private Collection<? extends BaseResourceIndexedSearchParam> findParameterIndexes(ResourceIndexedSearchParams theParams, RuntimeSearchParam nextCompositeOf) {
Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
switch (nextCompositeOf.getParamType()) {
case NUMBER:
paramsListForCompositePart = theParams.myNumberParams;
break;
case DATE:
paramsListForCompositePart = theParams.myDateParams;
break;
case STRING:
paramsListForCompositePart = theParams.myStringParams;
break;
case TOKEN:
paramsListForCompositePart = theParams.myTokenParams;
break;
case QUANTITY:
paramsListForCompositePart = theParams.myQuantityParams;
break;
case URI:
paramsListForCompositePart = theParams.myUriParams;
break;
case REFERENCE:
case SPECIAL:
case COMPOSITE:
case HAS:
break;
}
if (paramsListForCompositePart != null) {
paramsListForCompositePart = paramsListForCompositePart
.stream()
.filter(t -> t.getParamName().equals(nextCompositeOf.getName()))
.collect(Collectors.toList());
}
return paramsListForCompositePart;
}
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);

View File

@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@ -38,6 +40,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public interface ISearchParamExtractor {
@ -61,6 +64,10 @@ public interface ISearchParamExtractor {
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource);
SearchParamSet<ResourceIndexedComboStringUnique> extractSearchParamComboUnique(String theResourceType, ResourceIndexedSearchParams theParams);
SearchParamSet<ResourceIndexedComboTokenNonUnique> extractSearchParamComboNonUnique(String theResourceType, ResourceIndexedSearchParams theParams);
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource);
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences);

View File

@ -31,10 +31,14 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.IResourceIndexComboSearchParameter;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@ -67,6 +71,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -587,6 +592,17 @@ public class SearchParamExtractorService {
}
}
private void populateResourceTableForComboParams(Collection<? extends IResourceIndexComboSearchParameter> theParams, ResourceTable theResourceTable) {
for (IResourceIndexComboSearchParameter next : theParams) {
if (next.getResource() == null) {
next.setResource(theResourceTable);
if (next instanceof BasePartitionable){
((BasePartitionable)next).setPartitionId(theResourceTable.getPartitionId());
}
}
}
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamDates(theResource);
}
@ -634,6 +650,20 @@ public class SearchParamExtractorService {
return mySearchParamExtractor.extractParamValuesAsStrings(theActiveSearchParam, theResource);
}
public void extractSearchParamComboUnique(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
String resourceType = theEntity.getResourceType();
Set<ResourceIndexedComboStringUnique> comboUniques = mySearchParamExtractor.extractSearchParamComboUnique(resourceType, theParams);
theParams.myComboStringUniques.addAll(comboUniques);
populateResourceTableForComboParams(theParams.myComboStringUniques, theEntity);
}
public void extractSearchParamComboNonUnique(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
String resourceType = theEntity.getResourceType();
Set<ResourceIndexedComboTokenNonUnique> comboNonUniques = mySearchParamExtractor.extractSearchParamComboNonUnique(resourceType, theParams);
theParams.myComboTokenNonUnique.addAll(comboNonUniques);
populateResourceTableForComboParams(theParams.myComboTokenNonUnique, theEntity);
}
static void handleWarnings(RequestDetails theRequestDetails, IInterceptorBroadcaster theInterceptorBroadcaster, ISearchParamExtractor.SearchParamSet<?> theSearchParamSet) {
if (theSearchParamSet.getWarnings().isEmpty()) {
return;

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.interceptor.api.HookParams;
@ -29,6 +30,7 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -39,16 +41,19 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaSearchParamCache {
private static final Logger ourLog = LoggerFactory.getLogger(JpaSearchParamCache.class);
private volatile Map<String, List<RuntimeSearchParam>> myActiveComboSearchParams = Collections.emptyMap();
private volatile Map<String, Map<Set<String>, List<RuntimeSearchParam>>> myActiveParamNamesToComboSearchParams = Collections.emptyMap();
volatile Map<String, List<RuntimeSearchParam>> myActiveComboSearchParams = Collections.emptyMap();
volatile Map<String, Map<Set<String>, List<RuntimeSearchParam>>> myActiveParamNamesToComboSearchParams = Collections.emptyMap();
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName) {
List<RuntimeSearchParam> retval = myActiveComboSearchParams.get(theResourceName);
@ -58,6 +63,20 @@ public class JpaSearchParamCache {
return retval;
}
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, ComboSearchParamType theParamType) {
return getActiveComboSearchParams(theResourceName)
.stream()
.filter(param -> Objects.equals(theParamType, param.getComboSearchParamType()))
.collect(Collectors.toList());
}
public Optional<RuntimeSearchParam> getActiveComboSearchParamById(String theResourceName, IIdType theId) {
return getActiveComboSearchParams(theResourceName)
.stream()
.filter((param) -> Objects.equals(theId, param.getId()))
.findFirst();
}
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
Map<Set<String>, List<RuntimeSearchParam>> paramNamesToParams = myActiveParamNamesToComboSearchParams.get(theResourceName);
if (paramNamesToParams == null) {

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
@ -57,6 +58,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -129,6 +131,11 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry, IResourceC
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName);
}
@Override
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, ComboSearchParamType theParamType) {
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamType);
}
@Override
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
return myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamNames);
@ -144,6 +151,12 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry, IResourceC
}
}
@Override
public Optional<RuntimeSearchParam> getActiveComboSearchParamById(String theResourceName, IIdType theId) {
return myJpaSearchParamCache.getActiveComboSearchParamById(theResourceName, theId);
}
private void rebuildActiveSearchParams() {
ourLog.info("Rebuilding SearchParamRegistry");
SearchParameterMap params = new SearchParameterMap();

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
@ -32,6 +34,7 @@ import org.hl7.fhir.dstu3.model.Location;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
@ -39,6 +42,7 @@ import javax.annotation.Nullable;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@ -305,6 +309,16 @@ public class SearchParamExtractorDstu3Test {
throw new UnsupportedOperationException();
}
@Override
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, ComboSearchParamType theParamType) {
throw new UnsupportedOperationException(Msg.code(2210));
}
@Override
public Optional<RuntimeSearchParam> getActiveComboSearchParamById(String theResourceName, IIdType theId) {
throw new UnsupportedOperationException(Msg.code(2212));
}
@Override
public void requestRefresh() {
// nothing

View File

@ -0,0 +1,117 @@
package ca.uhn.fhir.jpa.searchparam.registry;
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.RuntimeSearchParam;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class JpaSearchParamCacheTest {
private static final String RESOURCE_TYPE = "Patient";
private TestableJpaSearchParamCache myJpaSearchParamCache;
@BeforeEach
public void beforeEach(){
myJpaSearchParamCache = new TestableJpaSearchParamCache();
}
@Test
public void testGetAllActiveComboParams(){
RuntimeSearchParam unique1 = createSearchParam(ComboSearchParamType.UNIQUE);
RuntimeSearchParam unique2 = createSearchParam(ComboSearchParamType.UNIQUE);
RuntimeSearchParam nonUnique1 = createSearchParam(ComboSearchParamType.NON_UNIQUE);
RuntimeSearchParam nonUnique2 = createSearchParam(ComboSearchParamType.NON_UNIQUE);
setActiveComboSearchParams(RESOURCE_TYPE, List.of(unique1, unique2, nonUnique1, nonUnique2));
List<RuntimeSearchParam> result = myJpaSearchParamCache.getActiveComboSearchParams(RESOURCE_TYPE);
assertEquals(4, result.size());
assertTrue(result.containsAll(List.of(unique1, unique2, nonUnique1, nonUnique2)));
}
@Test
public void testGetUniqueActiveComboParams(){
RuntimeSearchParam unique1 = createSearchParam(ComboSearchParamType.UNIQUE);
RuntimeSearchParam unique2 = createSearchParam(ComboSearchParamType.UNIQUE);
RuntimeSearchParam nonUnique = createSearchParam(ComboSearchParamType.NON_UNIQUE);
setActiveComboSearchParams(RESOURCE_TYPE, List.of(unique1, unique2, nonUnique));
List<RuntimeSearchParam> result = myJpaSearchParamCache.getActiveComboSearchParams(RESOURCE_TYPE, ComboSearchParamType.UNIQUE);
assertEquals(2, result.size());
assertTrue(result.containsAll(List.of(unique1, unique2)));
}
@Test
public void testGetNonUniqueActiveComboParams(){
RuntimeSearchParam nonUnique1 = createSearchParam(ComboSearchParamType.NON_UNIQUE);
RuntimeSearchParam nonUnique2 = createSearchParam(ComboSearchParamType.NON_UNIQUE);
RuntimeSearchParam unique = createSearchParam(ComboSearchParamType.UNIQUE);
setActiveComboSearchParams(RESOURCE_TYPE, List.of(nonUnique1, nonUnique2, unique));
List<RuntimeSearchParam> result = myJpaSearchParamCache.getActiveComboSearchParams(RESOURCE_TYPE, ComboSearchParamType.NON_UNIQUE);
assertEquals(2, result.size());
assertTrue(result.containsAll(List.of(nonUnique1, nonUnique2)));
}
@Test
public void testGetActiveComboParamByIdPresent(){
IIdType id1 = new IdType(1);
RuntimeSearchParam sp1 = createSearchParam(id1, ComboSearchParamType.NON_UNIQUE);
IIdType id2 = new IdType(2);
RuntimeSearchParam sp2 = createSearchParam(id2, ComboSearchParamType.NON_UNIQUE);
setActiveComboSearchParams(RESOURCE_TYPE, List.of(sp1, sp2));
Optional<RuntimeSearchParam> found = myJpaSearchParamCache.getActiveComboSearchParamById(RESOURCE_TYPE, id1);
assertTrue(found.isPresent());
assertEquals(id1, found.get().getId());
}
@Test
public void testGetActiveComboParamByIdAbsent(){
IIdType id1 = new IdType(1);
RuntimeSearchParam sp1 = createSearchParam(id1, ComboSearchParamType.NON_UNIQUE);
IIdType id2 = new IdType(2);
setActiveComboSearchParams(RESOURCE_TYPE, List.of(sp1));
Optional<RuntimeSearchParam> found = myJpaSearchParamCache.getActiveComboSearchParamById(RESOURCE_TYPE, id2);
assertTrue(found.isEmpty());
}
private RuntimeSearchParam createSearchParam(ComboSearchParamType theType){
return createSearchParam(null, theType);
}
private RuntimeSearchParam createSearchParam(IIdType theId, ComboSearchParamType theType){
RuntimeSearchParam sp = mock(RuntimeSearchParam.class);
when(sp.getId()).thenReturn(theId);
when(sp.getComboSearchParamType()).thenReturn(theType);
return sp;
}
private void setActiveComboSearchParams(String theResourceType, List<RuntimeSearchParam> theRuntimeSearchParams) {
Map<String, List<RuntimeSearchParam>> activeComboParams = new HashMap<>();
activeComboParams.put(theResourceType, theRuntimeSearchParams);
myJpaSearchParamCache.setActiveComboSearchParams(activeComboParams);
}
private class TestableJpaSearchParamCache extends JpaSearchParamCache {
public void setActiveComboSearchParams(Map<String, List<RuntimeSearchParam>> theActiveComboSearchParams){
myActiveComboSearchParams = theActiveComboSearchParams;
}
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,9 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.SearchParameter;
@ -30,6 +32,8 @@ public class JpaResourceDaoSearchParameterTest {
private JpaResourceDaoSearchParameter<SearchParameter> myDao;
@Mock
private ApplicationContext myApplicationContext;
@Mock
private ISearchParamRegistry mySearchParamRegistry;
@BeforeEach
public void before() {
@ -39,10 +43,13 @@ public class JpaResourceDaoSearchParameterTest {
myDao = new JpaResourceDaoSearchParameter<>();
myDao.setContext(myCtx);
DaoConfig defaultConfig = new DaoConfig();
myDao.setDaoConfigForUnitTest(defaultConfig);
myDao.setResourceType(SearchParameter.class);
myDao.setDaoConfigForUnitTest(new DaoConfig());
myDao.setApplicationContext(myApplicationContext);
myDao.setVersionCanonicalizerForUnitTest(versionCanonicalizer);
SearchParameterDaoValidator validator = new SearchParameterDaoValidator(myCtx, defaultConfig, mySearchParamRegistry);
myDao.setSearchParameterDaoValidatorForUnitTest(validator);
myDao.start();
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,17 +20,20 @@ package ca.uhn.fhir.rest.server.util;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class FhirContextSearchParamRegistry implements ISearchParamRegistry {
@ -97,6 +100,16 @@ public class FhirContextSearchParamRegistry implements ISearchParamRegistry {
throw new UnsupportedOperationException(Msg.code(2068));
}
@Override
public List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, ComboSearchParamType theParamType) {
throw new UnsupportedOperationException(Msg.code(2209));
}
@Override
public Optional<RuntimeSearchParam> getActiveComboSearchParamById(String theResourceName, IIdType theId) {
throw new UnsupportedOperationException(Msg.code(2211));
}
@Override
public void requestRefresh() {
// nothing

View File

@ -20,17 +20,20 @@ package ca.uhn.fhir.rest.server.util;
* #L%
*/
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@ -73,6 +76,17 @@ public interface ISearchParamRegistry {
return Collections.emptyList();
}
// TODO ND remove default implementation
default List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, ComboSearchParamType theParamType) {
return Collections.emptyList();
}
// TODO ND remove default implementation
default Optional<RuntimeSearchParam> getActiveComboSearchParamById(String theResourceName, IIdType theId) {
return Optional.empty();
}
default List<RuntimeSearchParam> getActiveComboSearchParams(String theResourceName, Set<String> theParamNames) {
return Collections.emptyList();
}

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -20,7 +20,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,146 @@
package ca.uhn.fhir.jpa.dao.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.SearchParameter;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParameterDaoValidator {
private static final Pattern REGEX_SP_EXPRESSION_HAS_PATH = Pattern.compile("[( ]*([A-Z][a-zA-Z]+\\.)?[a-z].*");
private final FhirContext myFhirContext;
private final DaoConfig myDaoConfig;
private final ISearchParamRegistry mySearchParamRegistry;
public SearchParameterDaoValidator(FhirContext theContext, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) {
myFhirContext = theContext;
myDaoConfig = theDaoConfig;
mySearchParamRegistry = theSearchParamRegistry;
}
public void validate(SearchParameter searchParameter) {
/*
* If overriding built-in SPs is disabled on this server, make sure we aren't
* doing that
*/
if (myDaoConfig.getModelConfig().isDefaultSearchParamsCanBeOverridden() == false) {
for (IPrimitiveType<?> nextBaseType : searchParameter.getBase()) {
String nextBase = nextBaseType.getValueAsString();
RuntimeSearchParam existingSearchParam = mySearchParamRegistry.getActiveSearchParam(nextBase, searchParameter.getCode());
if (existingSearchParam != null) {
boolean isBuiltIn = existingSearchParam.getId() == null;
isBuiltIn |= existingSearchParam.getUri().startsWith("http://hl7.org/fhir/SearchParameter/");
if (isBuiltIn) {
throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + searchParameter.getCode() + " because overriding is disabled on this server");
}
}
}
}
/*
* Everything below is validating that the SP is actually valid. We'll only do that if the
* SPO is active, so that we don't block people from uploading works-in-progress
*/
if (searchParameter.getStatus() == null) {
throw new UnprocessableEntityException(Msg.code(1112) + "SearchParameter.status is missing or invalid");
}
if (!searchParameter.getStatus().name().equals("ACTIVE")) {
return;
}
if (isCompositeWithoutBase(searchParameter)) {
throw new UnprocessableEntityException(Msg.code(1113) + "SearchParameter.base is missing");
}
boolean isUnique = hasAnyExtensionUniqueSetTo(searchParameter, true);
if (isCompositeWithoutExpression(searchParameter)) {
// this is ok
} else if (isBlank(searchParameter.getExpression())) {
throw new UnprocessableEntityException(Msg.code(1114) + "SearchParameter.expression is missing");
} 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();
if (fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
// omitting validation for DSTU2_HL7ORG, DSTU2_1 and DSTU2
} else {
if (myDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
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 isCompositeWithoutBase(SearchParameter searchParameter) {
return ElementUtil.isEmpty(searchParameter.getBase()) && (searchParameter.getType() == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(searchParameter.getType().name()));
}
private boolean isCompositeWithoutExpression(SearchParameter searchParameter) {
return searchParameter.getType() != null && searchParameter.getType().name().equals(Enumerations.SearchParamType.COMPOSITE.name()) && isBlank(searchParameter.getExpression());
}
private void validateExpressionPath(SearchParameter theSearchParameter) {
String expression = getExpression(theSearchParameter);
boolean isResourceOfTypeComposite = theSearchParameter.getType() == Enumerations.SearchParamType.COMPOSITE;
boolean isResourceOfTypeSpecial = theSearchParameter.getType() == Enumerations.SearchParamType.SPECIAL;
boolean expressionHasPath = REGEX_SP_EXPRESSION_HAS_PATH.matcher(expression).matches();
boolean isUnique = hasAnyExtensionUniqueSetTo(theSearchParameter, true);
if (!isUnique && !isResourceOfTypeComposite && !isResourceOfTypeSpecial && !expressionHasPath) {
throw new UnprocessableEntityException(Msg.code(1120) + "SearchParameter.expression value \"" + expression + "\" is invalid due to missing/incorrect path");
}
}
private String getExpression(SearchParameter theSearchParameter) {
return theSearchParameter.getExpression().trim();
}
private boolean hasAnyExtensionUniqueSetTo(SearchParameter theSearchParameter, boolean theValue) {
String theValueAsString = Boolean.toString(theValue);
return theSearchParameter
.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE)
.stream()
.anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description>
<url>https://hapifhir.io</url>
@ -2110,7 +2110,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-checkstyle</artifactId>
<!-- Remember to bump this when you upgrade the version -->
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.3-SNAPSHOT</version>
<version>6.3.4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>