ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor Maven / Gradle / Ivy
/*
* #%L
* HAPI FHIR JPA - Search Parameters
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
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;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.Msg;
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.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;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
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.entity.StorageSettings;
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.jpa.searchparam.util.RuntimeSearchParamHelper;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
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 jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.text.StringTokenizer;
import org.fhir.ucum.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.measure.quantity.Quantity;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.DATE;
import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.REFERENCE;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
public static final Set COORDS_INDEX_PATHS;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
static {
Set coordsIndexPaths = Sets.newHashSet("Location.position");
COORDS_INDEX_PATHS = Collections.unmodifiableSet(coordsIndexPaths);
}
@Autowired
protected ApplicationContext myApplicationContext;
@Autowired
private FhirContext myContext;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private StorageSettings myStorageSettings;
@Autowired
private PartitionSettings myPartitionSettings;
private Set myIgnoredForSearchDatatypes;
private BaseRuntimeChildDefinition myQuantityValueValueChild;
private BaseRuntimeChildDefinition myQuantitySystemValueChild;
private BaseRuntimeChildDefinition myQuantityCodeValueChild;
private BaseRuntimeChildDefinition myMoneyValueChild;
private BaseRuntimeChildDefinition myMoneyCurrencyChild;
private BaseRuntimeElementCompositeDefinition> myLocationPositionDefinition;
private BaseRuntimeChildDefinition myCodeSystemUrlValueChild;
private BaseRuntimeChildDefinition myRangeLowValueChild;
private BaseRuntimeChildDefinition myRangeHighValueChild;
private BaseRuntimeChildDefinition myAddressLineValueChild;
private BaseRuntimeChildDefinition myAddressCityValueChild;
private BaseRuntimeChildDefinition myAddressDistrictValueChild;
private BaseRuntimeChildDefinition myAddressStateValueChild;
private BaseRuntimeChildDefinition myAddressCountryValueChild;
private BaseRuntimeChildDefinition myAddressPostalCodeValueChild;
private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild;
private BaseRuntimeChildDefinition myPeriodStartValueChild;
private BaseRuntimeChildDefinition myPeriodEndValueChild;
private BaseRuntimeChildDefinition myTimingEventValueChild;
private BaseRuntimeChildDefinition myTimingRepeatValueChild;
private BaseRuntimeChildDefinition myTimingRepeatBoundsValueChild;
private BaseRuntimeChildDefinition myDurationSystemValueChild;
private BaseRuntimeChildDefinition myDurationCodeValueChild;
private BaseRuntimeChildDefinition myDurationValueValueChild;
private BaseRuntimeChildDefinition myHumanNameFamilyValueChild;
private BaseRuntimeChildDefinition myHumanNameGivenValueChild;
private BaseRuntimeChildDefinition myHumanNameTextValueChild;
private BaseRuntimeChildDefinition myHumanNamePrefixValueChild;
private BaseRuntimeChildDefinition myHumanNameSuffixValueChild;
private BaseRuntimeChildDefinition myContactPointValueValueChild;
private BaseRuntimeChildDefinition myIdentifierSystemValueChild;
private BaseRuntimeChildDefinition myIdentifierValueValueChild;
private BaseRuntimeChildDefinition myIdentifierTypeValueChild;
private BaseRuntimeChildDefinition myIdentifierTypeTextValueChild;
private BaseRuntimeChildDefinition myCodeableConceptCodingValueChild;
private BaseRuntimeChildDefinition myCodeableConceptTextValueChild;
private BaseRuntimeChildDefinition myCodingSystemValueChild;
private BaseRuntimeChildDefinition myCodingCodeValueChild;
private BaseRuntimeChildDefinition myCodingDisplayValueChild;
private BaseRuntimeChildDefinition myContactPointSystemValueChild;
private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild;
private BaseRuntimeChildDefinition myCodeableReferenceConcept;
private BaseRuntimeChildDefinition myCodeableReferenceReference;
// allow extraction of Resource-level search param values
private boolean myExtractResourceLevelParams = false;
/**
* Constructor
*/
BaseSearchParamExtractor() {
super();
}
/**
* UNIT TEST constructor
*/
BaseSearchParamExtractor(
StorageSettings theStorageSettings,
PartitionSettings thePartitionSettings,
FhirContext theCtx,
ISearchParamRegistry theSearchParamRegistry) {
Objects.requireNonNull(theStorageSettings);
Objects.requireNonNull(theCtx);
Objects.requireNonNull(theSearchParamRegistry);
myStorageSettings = theStorageSettings;
myContext = theCtx;
mySearchParamRegistry = theSearchParamRegistry;
myPartitionSettings = thePartitionSettings;
}
@Override
public SearchParamSet extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) {
IExtractor extractor = createReferenceExtractor();
return extractSearchParams(
theResource,
extractor,
RestSearchParameterTypeEnum.REFERENCE,
theWantLocalReferences,
ISearchParamExtractor.ALL_PARAMS);
}
private IExtractor createReferenceExtractor() {
return new ResourceLinkExtractor();
}
@Override
public PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath) {
ResourceLinkExtractor extractor = new ResourceLinkExtractor();
return extractor.get(theValue, thePath);
}
@Override
public List extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) {
IExtractor extractor = createExtractor(theSearchParam, theResource);
if (theSearchParam.getParamType().equals(REFERENCE)) {
return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor);
} else {
return extractParamsAsQueryTokens(theSearchParam, theResource, extractor);
}
}
@Nonnull
private IExtractor createExtractor(RuntimeSearchParam theSearchParam, IBaseResource theResource) {
IExtractor extractor;
switch (theSearchParam.getParamType()) {
case DATE:
extractor = createDateExtractor(theResource);
break;
case STRING:
extractor = createStringExtractor(theResource);
break;
case TOKEN:
extractor = createTokenExtractor(theResource);
break;
case NUMBER:
extractor = createNumberExtractor(theResource);
break;
case REFERENCE:
extractor = createReferenceExtractor();
break;
case QUANTITY:
extractor = createQuantityExtractor(theResource);
break;
case URI:
extractor = createUriExtractor(theResource);
break;
case SPECIAL:
extractor = createSpecialExtractor(theResource.getIdElement().getResourceType());
break;
case COMPOSITE:
case HAS:
default:
throw new UnsupportedOperationException(
Msg.code(503) + "Type " + theSearchParam.getParamType() + " not supported for extraction");
}
return extractor;
}
private List extractReferenceParamsAsQueryTokens(
RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor theExtractor) {
SearchParamSet params = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
return refsToStringList(params);
}
private List refsToStringList(SearchParamSet theParams) {
return theParams.stream()
.map(PathAndRef::getRef)
.map(ref -> ref.getReferenceElement().toUnqualifiedVersionless().getValue())
.collect(Collectors.toList());
}
private List extractParamsAsQueryTokens(
RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor theExtractor) {
SearchParamSet params = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
return toStringList(params);
}
private List toStringList(SearchParamSet theParams) {
return theParams.stream()
.map(param -> param.toQueryParameterType().getValueAsQueryToken(myContext))
.collect(Collectors.toList());
}
@Override
public SearchParamSet extractSearchParamComposites(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createCompositeExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theSearchParamFilter);
}
private IExtractor createCompositeExtractor(IBaseResource theResource) {
return new CompositeExtractor(theResource);
}
/**
* Extractor for composite SPs.
* Extracts elements, and then recurses and applies extractors for each component SP using the element as the new root.
*/
public class CompositeExtractor implements IExtractor {
final IBaseResource myResource;
final String myResourceType;
public CompositeExtractor(IBaseResource theResource) {
myResource = theResource;
myResourceType = toRootTypeName(theResource);
}
/**
* Extract the subcomponent index data for each component of a composite SP from an IBase element.
*
* @param theParams will add 1 or 0 ResourceIndexedSearchParamComposite instances for theValue
* @param theCompositeSearchParam the composite SP
* @param theValue the focus element for the subcomponent extraction
* @param thePath unused api param
* @param theWantLocalReferences passed down to reference extraction
*/
@Override
public void extract(
SearchParamSet theParams,
RuntimeSearchParam theCompositeSearchParam,
IBase theValue,
String thePath,
boolean theWantLocalReferences) {
// skip broken SPs
if (!isExtractableComposite(theCompositeSearchParam)) {
ourLog.info(
"CompositeExtractor - skipping unsupported search parameter {}",
theCompositeSearchParam.getName());
return;
}
String compositeSpName = theCompositeSearchParam.getName();
ourLog.trace("CompositeExtractor - extracting {} {}", compositeSpName, theValue);
ResourceIndexedSearchParamComposite e = new ResourceIndexedSearchParamComposite(compositeSpName, thePath);
// extract the index data for each component.
for (RuntimeSearchParam.Component component : theCompositeSearchParam.getComponents()) {
String componentSpRef = component.getReference();
String expression = component.getExpression();
RuntimeSearchParam componentSp = mySearchParamRegistry.getActiveSearchParamByUrl(componentSpRef);
Validate.notNull(
componentSp,
"Misconfigured SP %s - failed to load component %s",
compositeSpName,
componentSpRef);
SearchParamSet componentIndexedSearchParams =
extractCompositeComponentIndexData(
theValue, componentSp, expression, theWantLocalReferences, theCompositeSearchParam);
if (componentIndexedSearchParams.isEmpty()) {
// If any of the components are empty, no search can ever match. Short circuit, and bail out.
return;
} else {
e.addComponentIndexedSearchParams(componentSp, componentIndexedSearchParams);
}
}
// every component has data. Add it for indexing.
theParams.add(e);
}
/**
* Extract the subcomponent index data for a single component of a composite SP.
*
* @param theFocusElement the element to use as the root for sub-extraction
* @param theComponentSearchParam the active subcomponent SP for extraction
* @param theSubPathExpression the sub-expression to extract values from theFocusElement
* @param theWantLocalReferences flag for URI processing
* @param theCompositeSearchParam the parent composite SP
* @return the extracted index beans for theFocusElement
*/
@Nonnull
private SearchParamSet extractCompositeComponentIndexData(
IBase theFocusElement,
RuntimeSearchParam theComponentSearchParam,
String theSubPathExpression,
boolean theWantLocalReferences,
RuntimeSearchParam theCompositeSearchParam) {
IExtractor componentExtractor = createExtractor(theComponentSearchParam, myResource);
SearchParamSet componentIndexData = new SearchParamSet<>();
extractSearchParam(
theComponentSearchParam,
theSubPathExpression,
theFocusElement,
componentExtractor,
componentIndexData,
theWantLocalReferences);
ourLog.trace(
"CompositeExtractor - extracted {} index values for {}",
componentIndexData.size(),
theComponentSearchParam.getName());
return componentIndexData;
}
/**
* Is this an extractable composite SP?
*
* @param theSearchParam of type composite
* @return can we extract useful index data from this?
*/
private boolean isExtractableComposite(RuntimeSearchParam theSearchParam) {
// this is a composite SP
return RestSearchParameterTypeEnum.COMPOSITE.equals(theSearchParam.getParamType())
&& theSearchParam.getComponents().stream().noneMatch(this::isNotExtractableCompositeComponent);
}
private boolean isNotExtractableCompositeComponent(RuntimeSearchParam.Component c) {
RuntimeSearchParam componentSearchParam = mySearchParamRegistry.getActiveSearchParamByUrl(c.getReference());
return // Does the sub-param link work?
componentSearchParam == null
||
// Is this the right type?
RestSearchParameterTypeEnum.COMPOSITE.equals(componentSearchParam.getParamType())
||
// Bug workaround: the component expressions are null in the FhirContextSearchParamRegistry. We
// can't do anything with them.
c.getExpression() == null
||
// TODO mb Bug workaround: we don't support the %resource variable, but standard SPs on
// MolecularSequence use it.
// Skip them for now.
c.getExpression().contains("%resource");
}
}
@Override
public SearchParamSet extractSearchParamComboUnique(
String theResourceType, ResourceIndexedSearchParams theParams) {
SearchParamSet retVal = new SearchParamSet<>();
List runtimeComboUniqueParams =
mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.UNIQUE);
for (RuntimeSearchParam runtimeParam : runtimeComboUniqueParams) {
Set comboUniqueParams =
createComboUniqueParam(theResourceType, theParams, runtimeParam);
retVal.addAll(comboUniqueParams);
}
return retVal;
}
private SearchParamSet createComboUniqueParam(
String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) {
SearchParamSet retVal = new SearchParamSet<>();
Set queryStringsToPopulate =
extractParameterCombinationsForComboParam(theParams, theResourceType, theRuntimeParam);
for (String nextQueryString : queryStringsToPopulate) {
ourLog.trace(
"Adding composite unique SP: {} on {} for {}",
nextQueryString,
theResourceType,
theRuntimeParam.getId());
ResourceIndexedComboStringUnique uniqueParam = new ResourceIndexedComboStringUnique();
uniqueParam.setIndexString(nextQueryString);
uniqueParam.setSearchParameterId(theRuntimeParam.getId());
retVal.add(uniqueParam);
}
return retVal;
}
@Override
public SearchParamSet extractSearchParamComboNonUnique(
String theResourceType, ResourceIndexedSearchParams theParams) {
SearchParamSet retVal = new SearchParamSet<>();
List runtimeComboNonUniqueParams =
mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.NON_UNIQUE);
for (RuntimeSearchParam runtimeParam : runtimeComboNonUniqueParams) {
Set comboNonUniqueParams =
createComboNonUniqueParam(theResourceType, theParams, runtimeParam);
retVal.addAll(comboNonUniqueParams);
}
return retVal;
}
private SearchParamSet createComboNonUniqueParam(
String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) {
SearchParamSet retVal = new SearchParamSet<>();
Set 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 extractParameterCombinationsForComboParam(
ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) {
List> partsChoices = new ArrayList<>();
List compositeComponents =
JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam);
for (RuntimeSearchParam nextCompositeOf : compositeComponents) {
Collection extends BaseResourceIndexedSearchParam> paramsListForCompositePart =
findParameterIndexes(theParams, nextCompositeOf);
Collection 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 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 nextChoicesList = new ArrayList<>();
partsChoices.add(nextChoicesList);
String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
if (paramsListForCompositePart != null) {
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE
&& nextParamAsClientParam instanceof DateParam) {
DateParam date = (DateParam) nextParamAsClientParam;
if (date.getPrecision() != TemporalPrecisionEnum.DAY) {
continue;
}
}
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 extractSearchParamTokens(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createTokenExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theSearchParamFilter);
}
@Override
public SearchParamSet extractSearchParamTokens(
IBaseResource theResource, RuntimeSearchParam theSearchParam) {
IExtractor extractor = createTokenExtractor(theResource);
SearchParamSet setToPopulate = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false);
return setToPopulate;
}
private IExtractor createTokenExtractor(IBaseResource theResource) {
String resourceTypeName = toRootTypeName(theResource);
String useSystem;
if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) {
if (resourceTypeName.equals("ValueSet")) {
ca.uhn.fhir.model.dstu2.resource.ValueSet dstu2ValueSet =
(ca.uhn.fhir.model.dstu2.resource.ValueSet) theResource;
useSystem = dstu2ValueSet.getCodeSystem().getSystem();
} else {
useSystem = null;
}
} else {
if (resourceTypeName.equals("CodeSystem")) {
useSystem = extractValueAsString(myCodeSystemUrlValueChild, theResource);
} else {
useSystem = null;
}
}
return new TokenExtractor(resourceTypeName, useSystem);
}
@Override
public SearchParamSet extractSearchParamSpecial(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
String resourceTypeName = toRootTypeName(theResource);
IExtractor extractor = createSpecialExtractor(resourceTypeName);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theSearchParamFilter);
}
private IExtractor createSpecialExtractor(String theResourceTypeName) {
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (COORDS_INDEX_PATHS.contains(path)) {
addCoords_Position(theResourceTypeName, params, searchParam, value);
}
};
}
private void addUnexpectedDatatypeWarning(
SearchParamSet> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
String typeDesc = myContext.getElementDefinition(theValue.getClass()).getName();
theParams.addWarning("Search param " + theSearchParam.getBase() + "#" + theSearchParam.getName()
+ " is unable to index value of type " + typeDesc + " as a "
+ theSearchParam.getParamType().name() + " at path: " + thePath);
}
@Override
public SearchParamSet extractSearchParamUri(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createUriExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.URI, false, theSearchParamFilter);
}
private IExtractor createUriExtractor(IBaseResource theResource) {
return (params, searchParam, value, path, theWantLocalReferences) -> {
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "uri":
case "url":
case "oid":
case "sid":
case "uuid":
addUri_Uri(resourceType, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
};
}
@Override
public SearchParamSet extractSearchParamDates(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createDateExtractor(theResource);
return extractSearchParams(theResource, extractor, DATE, false, theSearchParamFilter);
}
private IExtractor createDateExtractor(IBaseResource theResource) {
return new DateExtractor(theResource);
}
@Override
public Date extractDateFromResource(IBase theValue, String thePath) {
DateExtractor extractor = new DateExtractor("DateType");
return extractor.get(theValue, thePath, false).getValueHigh();
}
@Override
public SearchParamSet extractSearchParamNumber(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createNumberExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theSearchParamFilter);
}
private IExtractor createNumberExtractor(IBaseResource theResource) {
return (params, searchParam, value, path, theWantLocalReferences) -> {
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "Duration":
addNumber_Duration(resourceType, params, searchParam, value);
break;
case "Quantity":
addNumber_Quantity(resourceType, params, searchParam, value);
break;
case "integer":
case "positiveInt":
case "unsignedInt":
addNumber_Integer(resourceType, params, searchParam, value);
break;
case "decimal":
addNumber_Decimal(resourceType, params, searchParam, value);
break;
case "Range":
addNumber_Range(resourceType, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
};
}
@Override
public SearchParamSet extractSearchParamQuantity(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createQuantityUnnormalizedExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter);
}
@Override
public SearchParamSet extractSearchParamQuantityNormalized(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor =
createQuantityNormalizedExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter);
}
@Nonnull
private IExtractor extends BaseResourceIndexedSearchParamQuantity> createQuantityExtractor(
IBaseResource theResource) {
IExtractor extends BaseResourceIndexedSearchParamQuantity> result;
if (myStorageSettings.getNormalizedQuantitySearchLevel().storageOrSearchSupported()) {
result = new MultiplexExtractor(
createQuantityUnnormalizedExtractor(theResource), createQuantityNormalizedExtractor(theResource));
} else {
result = createQuantityUnnormalizedExtractor(theResource);
}
return result;
}
@Nonnull
private IExtractor createQuantityUnnormalizedExtractor(
IBaseResource theResource) {
String resourceType = toRootTypeName(theResource);
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
}
String nextType = toRootTypeName(value);
switch (nextType) {
case "Quantity":
addQuantity_Quantity(resourceType, params, searchParam, value);
break;
case "Money":
addQuantity_Money(resourceType, params, searchParam, value);
break;
case "Range":
addQuantity_Range(resourceType, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
};
}
private IExtractor createQuantityNormalizedExtractor(
IBaseResource theResource) {
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
}
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "Quantity":
addQuantity_QuantityNormalized(resourceType, params, searchParam, value);
break;
case "Money":
addQuantity_MoneyNormalized(resourceType, params, searchParam, value);
break;
case "Range":
addQuantity_RangeNormalized(resourceType, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
};
}
@Override
public SearchParamSet extractSearchParamStrings(
IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor extractor = createStringExtractor(theResource);
return extractSearchParams(
theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theSearchParamFilter);
}
private IExtractor createStringExtractor(IBaseResource theResource) {
String resourceType = toRootTypeName(theResource);
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (value instanceof IPrimitiveType) {
IPrimitiveType> nextValue = (IPrimitiveType>) value;
String valueAsString = nextValue.getValueAsString();
createStringIndexIfNotBlank(resourceType, params, searchParam, valueAsString);
return;
}
String nextType = toRootTypeName(value);
switch (nextType) {
case "HumanName":
addString_HumanName(resourceType, params, searchParam, value);
break;
case "Address":
addString_Address(resourceType, params, searchParam, value);
break;
case "ContactPoint":
addString_ContactPoint(resourceType, params, searchParam, value);
break;
case "Quantity":
addString_Quantity(resourceType, params, searchParam, value);
break;
case "Range":
addString_Range(resourceType, params, searchParam, value);
break;
case "Period":
// Condition.onset[x] can have a Period - Ignored for now
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
};
}
/**
* Override parent because we're using FHIRPath here
*/
@Override
public List extractValues(String thePaths, IBase theResource) {
List values = new ArrayList<>();
if (isNotBlank(thePaths)) {
String[] nextPathsSplit = split(thePaths);
for (String nextPath : nextPathsSplit) {
List extends IBase> allValues;
// This path is hard to parse and isn't likely to produce anything useful anyway
if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU2)
&& nextPath.equals("Bundle.entry.resource(0)")) {
continue;
}
nextPath = trim(nextPath);
IValueExtractor allValuesFunc = getPathValueExtractor(theResource, nextPath);
try {
allValues = allValuesFunc.get();
} catch (Exception e) {
String msg = getContext()
.getLocalizer()
.getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
throw new InternalErrorException(Msg.code(504) + msg, e);
}
values.addAll(allValues);
}
for (int i = 0; i < values.size(); i++) {
IBase nextObject = values.get(i);
if (nextObject instanceof IBaseExtension) {
IBaseExtension, ?> nextExtension = (IBaseExtension, ?>) nextObject;
nextObject = nextExtension.getValue();
values.set(i, nextObject);
}
}
}
return values;
}
protected FhirContext getContext() {
return myContext;
}
@VisibleForTesting
public void setContext(FhirContext theContext) {
myContext = theContext;
}
protected StorageSettings getStorageSettings() {
return myStorageSettings;
}
@VisibleForTesting
public void setStorageSettings(StorageSettings theStorageSettings) {
myStorageSettings = theStorageSettings;
}
@VisibleForTesting
public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
@VisibleForTesting
Collection getSearchParams(IBaseResource theResource) {
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
Collection retVal =
mySearchParamRegistry.getActiveSearchParams(def.getName()).values();
List defaultList = Collections.emptyList();
retVal = ObjectUtils.defaultIfNull(retVal, defaultList);
return retVal;
}
private void addQuantity_Quantity(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional> valueField =
myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(
myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
theParams.add(nextEntity);
}
}
private void addQuantity_QuantityNormalized(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional> valueField =
myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
// -- convert the value/unit to the canonical form if any
Pair canonicalForm = UcumServiceUtil.getCanonicalForm(system, nextValueValue, code);
if (canonicalForm != null) {
double canonicalValue =
Double.parseDouble(canonicalForm.getValue().asDecimal());
String canonicalUnits = canonicalForm.getCode();
ResourceIndexedSearchParamQuantityNormalized nextEntity =
new ResourceIndexedSearchParamQuantityNormalized(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
canonicalValue,
system,
canonicalUnits);
theParams.add(nextEntity);
}
}
}
private void addQuantity_Money(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional> valueField =
myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(
myPartitionSettings,
theResourceType,
searchParamName,
nextValueValue,
nextValueString,
nextValueCode);
theParams.add(nextEntity);
}
}
private void addQuantity_MoneyNormalized(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional> valueField =
myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized =
new ResourceIndexedSearchParamQuantityNormalized(
myPartitionSettings,
theResourceType,
searchParamName,
nextValueValue.doubleValue(),
nextValueString,
nextValueCode);
theParams.add(nextEntityNormalized);
}
}
private void addQuantity_Range(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
Optional high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
}
private void addQuantity_RangeNormalized(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
Optional high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(
theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
}
@SuppressWarnings("unchecked")
private void addToken_Identifier(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
String system = extractValueAsString(myIdentifierSystemValueChild, theValue);
String value = extractValueAsString(myIdentifierValueValueChild, theValue);
if (isNotBlank(value)) {
createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
boolean indexIdentifierType = myStorageSettings.isIndexIdentifierOfType();
if (indexIdentifierType) {
Optional type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue);
if (type.isPresent()) {
List codings =
myCodeableConceptCodingValueChild.getAccessor().getValues(type.get());
for (IBase nextCoding : codings) {
String typeSystem = myCodingSystemValueChild
.getAccessor()
.getFirstValueOrNull(nextCoding)
.map(t -> ((IPrimitiveType) t).getValue())
.orElse(null);
String typeValue = myCodingCodeValueChild
.getAccessor()
.getFirstValueOrNull(nextCoding)
.map(t -> ((IPrimitiveType) t).getValue())
.orElse(null);
if (isNotBlank(typeSystem) && isNotBlank(typeValue)) {
String paramName = theSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE;
ResourceIndexedSearchParamToken token = createTokenIndexIfNotBlank(
theResourceType, typeSystem, typeValue + "|" + value, paramName);
if (token != null) {
theParams.add(token);
}
}
}
}
}
}
}
protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) {
return tokenTextIndexingEnabledForSearchParam(myStorageSettings, theSearchParam);
}
private void addToken_CodeableConcept(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List codings = getCodingsFromCodeableConcept(theValue);
for (IBase nextCoding : codings) {
addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding);
}
if (shouldIndexTextComponentOfToken(theSearchParam)) {
String text = getDisplayTextFromCodeableConcept(theValue);
if (isNotBlank(text)) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
}
}
}
@Override
public List getCodingsFromCodeableConcept(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("CodeableConcept".equals(nextType)) {
return myCodeableConceptCodingValueChild.getAccessor().getValues(theValue);
} else {
return null;
}
}
@Override
public String getDisplayTextFromCodeableConcept(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("CodeableConcept".equals(nextType)) {
return extractValueAsString(myCodeableConceptTextValueChild, theValue);
} else {
return null;
}
}
private void addToken_Coding(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
ResourceIndexedSearchParamToken resourceIndexedSearchParamToken =
createSearchParamForCoding(theResourceType, theSearchParam, theValue);
if (resourceIndexedSearchParamToken != null) {
theParams.add(resourceIndexedSearchParamToken);
}
if (shouldIndexTextComponentOfToken(theSearchParam)) {
String text = getDisplayTextForCoding(theValue);
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
}
}
@Override
public ResourceIndexedSearchParamToken createSearchParamForCoding(
String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("Coding".equals(nextType)) {
String system = extractValueAsString(myCodingSystemValueChild, theValue);
String code = extractValueAsString(myCodingCodeValueChild, theValue);
return createTokenIndexIfNotBlank(theResourceType, system, code, theSearchParam.getName());
} else {
return null;
}
}
@Override
public String getDisplayTextForCoding(IBase theValue) {
String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
if ("Coding".equals(nextType)) {
return extractValueAsString(myCodingDisplayValueChild, theValue);
} else {
return null;
}
}
private void addToken_ContactPoint(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
String system = extractValueAsString(myContactPointSystemValueChild, theValue);
String value = extractValueAsString(myContactPointValueValueChild, theValue);
createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
}
private void addToken_CodeableReference(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional conceptOpt = myCodeableReferenceConcept.getAccessor().getFirstValueOrNull(theValue);
conceptOpt.ifPresent(concept -> addToken_CodeableConcept(theResourceType, theParams, theSearchParam, concept));
}
private void addToken_PatientCommunication(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List values =
myPatientCommunicationLanguageValueChild.getAccessor().getValues(theValue);
for (IBase next : values) {
addToken_CodeableConcept(theResourceType, theParams, theSearchParam, next);
}
}
private void addToken_CapabilityStatementRestSecurity(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List values =
myCapabilityStatementRestSecurityServiceValueChild.getAccessor().getValues(theValue);
for (IBase nextValue : values) {
addToken_CodeableConcept(theResourceType, theParams, theSearchParam, nextValue);
}
}
private void addDate_Period(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
if (start == null) {
start = myStorageSettings.getPeriodIndexStartOfTime().getValue();
startAsString = myStorageSettings.getPeriodIndexStartOfTime().getValueAsString();
}
if (end == null) {
end = myStorageSettings.getPeriodIndexEndOfTime().getValue();
endAsString = myStorageSettings.getPeriodIndexEndOfTime().getValueAsString();
}
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
start,
startAsString,
end,
endAsString,
startAsString);
theParams.add(nextEntity);
}
}
private void addDate_Timing(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
TreeSet dates = new TreeSet<>();
String firstValue = null;
String finalValue = null;
for (IPrimitiveType nextEvent : values) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
finalValue = nextEvent.getValueAsString();
}
}
Optional repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
if (repeat.isPresent()) {
Optional bounds =
myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
if (bounds.isPresent()) {
String boundsType = toRootTypeName(bounds.get());
if ("Period".equals(boundsType)) {
Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
String endString = extractValueAsString(myPeriodEndValueChild, bounds.get());
dates.add(start);
dates.add(end);
// TODO Check if this logic is valid. Does the start of the first period indicate a lower bound??
if (firstValue == null) {
firstValue = extractValueAsString(myPeriodStartValueChild, bounds.get());
}
finalValue = endString;
}
}
}
if (!dates.isEmpty()) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
dates.first(),
firstValue,
dates.last(),
finalValue,
firstValue);
theParams.add(nextEntity);
}
}
private void addNumber_Duration(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
String system = extractValueAsString(myDurationSystemValueChild, theValue);
String code = extractValueAsString(myDurationCodeValueChild, theValue);
BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue);
if (value != null) {
value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(
myPartitionSettings, theResourceType, theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
private void addNumber_Quantity(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue);
if (value != null) {
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(
myPartitionSettings, theResourceType, theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
private void addNumber_Range(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(value -> addNumber_Quantity(theResourceType, theParams, theSearchParam, value));
Optional high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(value -> addNumber_Quantity(theResourceType, theParams, theSearchParam, value));
}
@SuppressWarnings("unchecked")
private void addNumber_Integer(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
IPrimitiveType value = (IPrimitiveType) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = new BigDecimal(value.getValue());
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(
myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
}
@SuppressWarnings("unchecked")
private void addNumber_Decimal(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
IPrimitiveType value = (IPrimitiveType) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = value.getValue();
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(
myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
}
private void addCoords_Position(
String theResourceType,
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
BigDecimal latitude = null;
BigDecimal longitude = null;
if (theValue instanceof org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) {
org.hl7.fhir.dstu3.model.Location.LocationPositionComponent value =
(org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) theValue;
latitude = value.getLatitude();
longitude = value.getLongitude();
} else if (theValue instanceof org.hl7.fhir.r4.model.Location.LocationPositionComponent) {
org.hl7.fhir.r4.model.Location.LocationPositionComponent value =
(org.hl7.fhir.r4.model.Location.LocationPositionComponent) theValue;
latitude = value.getLatitude();
longitude = value.getLongitude();
} else if (theValue instanceof org.hl7.fhir.r5.model.Location.LocationPositionComponent) {
org.hl7.fhir.r5.model.Location.LocationPositionComponent value =
(org.hl7.fhir.r5.model.Location.LocationPositionComponent) theValue;
latitude = value.getLatitude();
longitude = value.getLongitude();
}
// We only accept coordinates when both are present
if (latitude != null && longitude != null) {
double normalizedLatitude = GeopointNormalizer.normalizeLatitude(latitude.doubleValue());
double normalizedLongitude = GeopointNormalizer.normalizeLongitude(longitude.doubleValue());
ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
normalizedLatitude,
normalizedLongitude);
theParams.add(nextEntity);
}
}
private void addString_HumanName(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List myHumanNameChildren = Arrays.asList(
myHumanNameFamilyValueChild,
myHumanNameGivenValueChild,
myHumanNameTextValueChild,
myHumanNamePrefixValueChild,
myHumanNameSuffixValueChild);
for (BaseRuntimeChildDefinition theChild : myHumanNameChildren) {
List indices = extractValuesAsStrings(theChild, theValue);
for (String next : indices) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next);
}
}
}
private void addString_Quantity(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue);
if (value != null) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString());
}
}
private void addString_Range(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Optional value = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
value.ifPresent(t -> addString_Quantity(theResourceType, theParams, theSearchParam, t));
}
private void addString_ContactPoint(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
String value = extractValueAsString(myContactPointValueValueChild, theValue);
if (isNotBlank(value)) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value);
}
}
private void addString_Address(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List allNames = new ArrayList<>(extractValuesAsStrings(myAddressLineValueChild, theValue));
String city = extractValueAsString(myAddressCityValueChild, theValue);
if (isNotBlank(city)) {
allNames.add(city);
}
String district = extractValueAsString(myAddressDistrictValueChild, theValue);
if (isNotBlank(district)) {
allNames.add(district);
}
String state = extractValueAsString(myAddressStateValueChild, theValue);
if (isNotBlank(state)) {
allNames.add(state);
}
String country = extractValueAsString(myAddressCountryValueChild, theValue);
if (isNotBlank(country)) {
allNames.add(country);
}
String postalCode = extractValueAsString(myAddressPostalCodeValueChild, theValue);
if (isNotBlank(postalCode)) {
allNames.add(postalCode);
}
for (String nextName : allNames) {
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, nextName);
}
}
/**
* Ignore any of the Resource-level search params. This is kind of awkward, but here is why
* we do it:
*
* The ReadOnlySearchParamCache supplies these params, and they have paths associated with
* them. E.g. HAPI's SearchParamRegistryImpl will know about the _id search parameter and
* assigns it the path "Resource.id". All of these parameters have indexing code paths in the
* server that don't rely on the existence of the SearchParameter. For example, we have a
* dedicated column on ResourceTable that handles the _id parameter.
*
* Until 6.2.0 the FhirPath evaluator didn't actually resolve any values for these paths
* that started with Resource instead of the actual resource name, so it never actually
* made a difference that these parameters existed because they'd never actually result
* in any index rows. In 6.4.0 that bug was fixed in the core FhirPath engine. We don't
* want that fix to result in pointless index rows for things like _id and _tag, so we
* ignore them here.
*
* Note that you can still create a search parameter that includes a path like
* "meta.tag" if you really need to create an SP that actually does index _tag. This
* is needed if you want to search for tags in INLINE
tag storage mode.
* This is the only way you could actually specify a FhirPath expression for those
* prior to 6.2.0 so this isn't a breaking change.
*/
SearchParamSet extractSearchParams(
IBaseResource theResource,
IExtractor theExtractor,
RestSearchParameterTypeEnum theSearchParamType,
boolean theWantLocalReferences,
ISearchParamFilter theSearchParamFilter) {
SearchParamSet retVal = new SearchParamSet<>();
Collection searchParams = getSearchParams(theResource);
int preFilterSize = searchParams.size();
Collection filteredSearchParams = theSearchParamFilter.filterSearchParams(searchParams);
assert filteredSearchParams.size() == preFilterSize || searchParams != filteredSearchParams;
cleanUpContainedResourceReferences(theResource, theSearchParamType, filteredSearchParams);
for (RuntimeSearchParam nextSpDef : filteredSearchParams) {
if (nextSpDef.getParamType() != theSearchParamType) {
continue;
}
// See the method javadoc for an explanation of this
if (!myExtractResourceLevelParams && RuntimeSearchParamHelper.isResourceLevel(nextSpDef)) {
continue;
}
extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences);
}
return retVal;
}
/**
* Helper function to determine if a set of SPs for a resource uses a resolve as part of its fhir path.
*/
private boolean anySearchParameterUsesResolve(
Collection searchParams, RestSearchParameterTypeEnum theSearchParamType) {
return searchParams.stream()
.filter(param -> param.getParamType() != theSearchParamType)
.map(RuntimeSearchParam::getPath)
.filter(Objects::nonNull)
.anyMatch(path -> path.contains("resolve"));
}
/**
* HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text
* (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the
* first way, so if there is any chance of the FHIRPath evaluator needing to descend across references, we
* have to assign values to those references before indexing.
*
* Doing this cleanup isn't hugely expensive, but it's not completely free either so we only do it
* if we think there's actually a chance
*/
private void cleanUpContainedResourceReferences(
IBaseResource theResource,
RestSearchParameterTypeEnum theSearchParamType,
Collection searchParams) {
boolean havePathWithResolveExpression = myStorageSettings.isIndexOnContainedResources()
|| anySearchParameterUsesResolve(searchParams, theSearchParamType);
if (havePathWithResolveExpression && myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) {
// TODO GGG/JA: At this point, if the Task.basedOn.reference.resource does _not_ have an ID, we will attempt
// to contain it internally. Wild
myContext
.newTerser()
.containResources(
theResource,
FhirTerser.OptionsEnum.MODIFY_RESOURCE,
FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
}
}
/**
* extract for normal SP
*/
@VisibleForTesting
public void extractSearchParam(
RuntimeSearchParam theSearchParameterDef,
IBase theResource,
IExtractor theExtractor,
SearchParamSet theSetToPopulate,
boolean theWantLocalReferences) {
String nextPathUnsplit = theSearchParameterDef.getPath();
extractSearchParam(
theSearchParameterDef,
nextPathUnsplit,
theResource,
theExtractor,
theSetToPopulate,
theWantLocalReferences);
}
/**
* extract for SP, but with possibly different expression.
* Allows composite SPs to use sub-paths.
*/
private void extractSearchParam(
RuntimeSearchParam theSearchParameterDef,
String thePathExpression,
IBase theResource,
IExtractor theExtractor,
SearchParamSet theSetToPopulate,
boolean theWantLocalReferences) {
if (isBlank(thePathExpression)) {
return;
}
String[] splitPaths = split(thePathExpression);
for (String nextPath : splitPaths) {
nextPath = trim(nextPath);
for (IBase nextObject : extractValues(nextPath, theResource)) {
if (nextObject != null) {
String typeName = toRootTypeName(nextObject);
if (!myIgnoredForSearchDatatypes.contains(typeName)) {
theExtractor.extract(
theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences);
}
}
}
}
}
@Override
public String toRootTypeName(IBase nextObject) {
BaseRuntimeElementDefinition> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
BaseRuntimeElementDefinition> rootParentDefinition = elementDefinition.getRootParentDefinition();
return rootParentDefinition.getName();
}
@Override
public String toTypeName(IBase nextObject) {
BaseRuntimeElementDefinition> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
return elementDefinition.getName();
}
private void addUri_Uri(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
IPrimitiveType> value = (IPrimitiveType>) theValue;
String valueAsString = value.getValueAsString();
if (isNotBlank(valueAsString)) {
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(
myPartitionSettings, theResourceType, theSearchParam.getName(), valueAsString);
theParams.add(nextEntity);
}
}
@SuppressWarnings({"UnnecessaryLocalVariable"})
private void createStringIndexIfNotBlank(
String theResourceType,
Set extends BaseResourceIndexedSearchParam> theParams,
RuntimeSearchParam theSearchParam,
String theValue) {
String value = theValue;
if (isNotBlank(value)) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
String searchParamName = theSearchParam.getName();
String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value);
String valueEncoded = theSearchParam.encode(valueNormalized);
if (valueEncoded.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
valueEncoded = valueEncoded.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(
myPartitionSettings, getStorageSettings(), theResourceType, searchParamName, valueEncoded, value);
Set params = theParams;
params.add(nextEntity);
}
}
private void createTokenIndexIfNotBlankAndAdd(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
String theSystem,
String theValue) {
ResourceIndexedSearchParamToken nextEntity =
createTokenIndexIfNotBlank(theResourceType, theSystem, theValue, theSearchParam.getName());
if (nextEntity != null) {
theParams.add(nextEntity);
}
}
@VisibleForTesting
public void setPartitionSettings(PartitionSettings thePartitionSettings) {
myPartitionSettings = thePartitionSettings;
}
private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(
String theResourceType, String theSystem, String theValue, String searchParamName) {
ResourceIndexedSearchParamToken nextEntity = null;
if (isNotBlank(theSystem) || isNotBlank(theValue)) {
nextEntity = new ResourceIndexedSearchParamToken(
myPartitionSettings, theResourceType, searchParamName, theSystem, theValue);
}
return nextEntity;
}
@Override
public String[] split(String thePaths) {
if (shouldAttemptToSplitPath(thePaths)) {
return splitOutOfParensOrs(thePaths);
} else {
return new String[] {thePaths};
}
}
public boolean shouldAttemptToSplitPath(String thePath) {
if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
return thePath.contains("|");
} else {
// DSTU 3 and below used "or" as well as "|"
return thePath.contains("|") || thePath.contains(" or ");
}
}
/**
* Iteratively splits a string on any ` or ` or | that is ** not** contained inside a set of parentheses. e.g.
*
* "Patient.select(a or b)" --> ["Patient.select(a or b)"]
* "Patient.select(a or b) or Patient.select(c or d )" --> ["Patient.select(a or b)", "Patient.select(c or d)"]
* "Patient.select(a|b) or Patient.select(c or d )" --> ["Patient.select(a|b)", "Patient.select(c or d)"]
* "Patient.select(b) | Patient.select(c)" --> ["Patient.select(b)", "Patient.select(c)"]
*
* @param thePaths The string to split
* @return The split string
*/
private String[] splitOutOfParensOrs(String thePaths) {
List topLevelOrExpressions = splitOutOfParensToken(thePaths, " or ");
return topLevelOrExpressions.stream()
.flatMap(s -> splitOutOfParensToken(s, " |").stream())
.toArray(String[]::new);
}
private List splitOutOfParensToken(String thePath, String theToken) {
int tokenLength = theToken.length();
int index = thePath.indexOf(theToken);
int rightIndex = 0;
List retVal = new ArrayList<>();
while (index > -1) {
String left = thePath.substring(rightIndex, index);
if (allParensHaveBeenClosed(left)) {
retVal.add(left);
rightIndex = index + tokenLength;
}
index = thePath.indexOf(theToken, index + tokenLength);
}
retVal.add(thePath.substring(rightIndex));
return retVal;
}
private boolean allParensHaveBeenClosed(String thePaths) {
int open = StringUtils.countMatches(thePaths, "(");
int close = StringUtils.countMatches(thePaths, ")");
return open == close;
}
private BigDecimal normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(
String theSystem, String theCode, BigDecimal theValue) {
if (SearchParamConstants.UCUM_NS.equals(theSystem)) {
if (isNotBlank(theCode)) {
Unit extends Quantity> unit = Unit.valueOf(theCode);
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(theValue.doubleValue());
theValue = new BigDecimal(dayValue);
}
}
return theValue;
}
@PostConstruct
public void start() {
myIgnoredForSearchDatatypes = new HashSet<>();
addIgnoredType(getContext(), "Annotation", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "Attachment", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "Count", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "Distance", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "Ratio", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "SampledData", myIgnoredForSearchDatatypes);
addIgnoredType(getContext(), "Signature", myIgnoredForSearchDatatypes);
/*
* This is building up an internal map of all the various field accessors we'll need in order to work
* with the model. This is kind of ugly, but we want to be as efficient as possible since
* search param extraction happens a whole heck of a lot at runtime..
*/
BaseRuntimeElementCompositeDefinition> quantityDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Quantity");
myQuantityValueValueChild = quantityDefinition.getChildByName("value");
myQuantitySystemValueChild = quantityDefinition.getChildByName("system");
myQuantityCodeValueChild = quantityDefinition.getChildByName("code");
BaseRuntimeElementCompositeDefinition> moneyDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Money");
myMoneyValueChild = moneyDefinition.getChildByName("value");
myMoneyCurrencyChild = moneyDefinition.getChildByName("currency");
BaseRuntimeElementCompositeDefinition> locationDefinition =
getContext().getResourceDefinition("Location");
BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position");
myLocationPositionDefinition =
(BaseRuntimeElementCompositeDefinition>) locationPositionValueChild.getChildByName("position");
BaseRuntimeElementCompositeDefinition> rangeDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Range");
myRangeLowValueChild = rangeDefinition.getChildByName("low");
myRangeHighValueChild = rangeDefinition.getChildByName("high");
BaseRuntimeElementCompositeDefinition> addressDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Address");
myAddressLineValueChild = addressDefinition.getChildByName("line");
myAddressCityValueChild = addressDefinition.getChildByName("city");
myAddressDistrictValueChild = addressDefinition.getChildByName("district");
myAddressStateValueChild = addressDefinition.getChildByName("state");
myAddressCountryValueChild = addressDefinition.getChildByName("country");
myAddressPostalCodeValueChild = addressDefinition.getChildByName("postalCode");
BaseRuntimeElementCompositeDefinition> periodDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Period");
myPeriodStartValueChild = periodDefinition.getChildByName("start");
myPeriodEndValueChild = periodDefinition.getChildByName("end");
BaseRuntimeElementCompositeDefinition> timingDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Timing");
myTimingEventValueChild = timingDefinition.getChildByName("event");
myTimingRepeatValueChild = timingDefinition.getChildByName("repeat");
BaseRuntimeElementCompositeDefinition> timingRepeatDefinition =
(BaseRuntimeElementCompositeDefinition>) myTimingRepeatValueChild.getChildByName("repeat");
myTimingRepeatBoundsValueChild = timingRepeatDefinition.getChildByName("bounds[x]");
BaseRuntimeElementCompositeDefinition> durationDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Duration");
myDurationSystemValueChild = durationDefinition.getChildByName("system");
myDurationCodeValueChild = durationDefinition.getChildByName("code");
myDurationValueValueChild = durationDefinition.getChildByName("value");
BaseRuntimeElementCompositeDefinition> humanNameDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("HumanName");
myHumanNameFamilyValueChild = humanNameDefinition.getChildByName("family");
myHumanNameGivenValueChild = humanNameDefinition.getChildByName("given");
myHumanNameTextValueChild = humanNameDefinition.getChildByName("text");
myHumanNamePrefixValueChild = humanNameDefinition.getChildByName("prefix");
myHumanNameSuffixValueChild = humanNameDefinition.getChildByName("suffix");
BaseRuntimeElementCompositeDefinition> contactPointDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("ContactPoint");
myContactPointValueValueChild = contactPointDefinition.getChildByName("value");
myContactPointSystemValueChild = contactPointDefinition.getChildByName("system");
BaseRuntimeElementCompositeDefinition> identifierDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Identifier");
myIdentifierSystemValueChild = identifierDefinition.getChildByName("system");
myIdentifierValueValueChild = identifierDefinition.getChildByName("value");
myIdentifierTypeValueChild = identifierDefinition.getChildByName("type");
BaseRuntimeElementCompositeDefinition> identifierTypeDefinition =
(BaseRuntimeElementCompositeDefinition>) myIdentifierTypeValueChild.getChildByName("type");
myIdentifierTypeTextValueChild = identifierTypeDefinition.getChildByName("text");
BaseRuntimeElementCompositeDefinition> codeableConceptDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("CodeableConcept");
myCodeableConceptCodingValueChild = codeableConceptDefinition.getChildByName("coding");
myCodeableConceptTextValueChild = codeableConceptDefinition.getChildByName("text");
BaseRuntimeElementCompositeDefinition> codingDefinition =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("Coding");
myCodingSystemValueChild = codingDefinition.getChildByName("system");
myCodingCodeValueChild = codingDefinition.getChildByName("code");
myCodingDisplayValueChild = codingDefinition.getChildByName("display");
BaseRuntimeElementCompositeDefinition> patientDefinition =
getContext().getResourceDefinition("Patient");
BaseRuntimeChildDefinition patientCommunicationValueChild = patientDefinition.getChildByName("communication");
BaseRuntimeElementCompositeDefinition> patientCommunicationDefinition =
(BaseRuntimeElementCompositeDefinition>)
patientCommunicationValueChild.getChildByName("communication");
myPatientCommunicationLanguageValueChild = patientCommunicationDefinition.getChildByName("language");
// DSTU3+
BaseRuntimeElementCompositeDefinition> codeSystemDefinition;
if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
codeSystemDefinition = getContext().getResourceDefinition("CodeSystem");
assert codeSystemDefinition != null;
myCodeSystemUrlValueChild = codeSystemDefinition.getChildByName("url");
BaseRuntimeElementCompositeDefinition> capabilityStatementDefinition =
getContext().getResourceDefinition("CapabilityStatement");
BaseRuntimeChildDefinition capabilityStatementRestChild =
capabilityStatementDefinition.getChildByName("rest");
BaseRuntimeElementCompositeDefinition> capabilityStatementRestDefinition =
(BaseRuntimeElementCompositeDefinition>) capabilityStatementRestChild.getChildByName("rest");
BaseRuntimeChildDefinition capabilityStatementRestSecurityValueChild =
capabilityStatementRestDefinition.getChildByName("security");
BaseRuntimeElementCompositeDefinition> capabilityStatementRestSecurityDefinition =
(BaseRuntimeElementCompositeDefinition>)
capabilityStatementRestSecurityValueChild.getChildByName("security");
myCapabilityStatementRestSecurityServiceValueChild =
capabilityStatementRestSecurityDefinition.getChildByName("service");
}
// R4B+
if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4B)) {
BaseRuntimeElementCompositeDefinition> codeableReferenceDef =
(BaseRuntimeElementCompositeDefinition>) getContext().getElementDefinition("CodeableReference");
myCodeableReferenceConcept = codeableReferenceDef.getChildByName("concept");
myCodeableReferenceReference = codeableReferenceDef.getChildByName("reference");
}
}
@FunctionalInterface
public interface IValueExtractor {
List extends IBase> get() throws FHIRException;
}
@VisibleForTesting
@FunctionalInterface
interface IExtractor {
void extract(
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
IBase theValue,
String thePath,
boolean theWantLocalReferences);
}
/**
* Note that this should only be called for R4+ servers. Prior to
* R4 the paths could be separated by the word "or" or by a "|"
* character, so we used a slower splitting mechanism.
*/
@Nonnull
public static String[] splitPathsR4(@Nonnull String thePaths) {
StringTokenizer tok = new StringTokenizer(thePaths, " |");
tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
return tok.getTokenArray();
}
public static boolean tokenTextIndexingEnabledForSearchParam(
StorageSettings theStorageSettings, RuntimeSearchParam theSearchParam) {
Optional noSuppressForSearchParam =
theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream()
.map(IBaseExtension::getValue)
.map(val -> (IPrimitiveType>) val)
.map(IPrimitiveType::getValueAsString)
.map(Boolean::parseBoolean)
.findFirst();
// if the SP doesn't care, use the system default.
if (noSuppressForSearchParam.isEmpty()) {
return !theStorageSettings.isSuppressStringIndexingInTokens();
// If the SP does care, use its value.
} else {
boolean suppressForSearchParam = noSuppressForSearchParam.get();
ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam);
return !suppressForSearchParam;
}
}
private static void addIgnoredType(FhirContext theCtx, String theType, Set theIgnoredTypes) {
BaseRuntimeElementDefinition> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
theIgnoredTypes.add(elementDefinition.getName());
}
}
protected static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValueAsString)
.orElse(null);
}
protected static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
protected static BigDecimal extractValueAsBigDecimal(
BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
@SuppressWarnings("unchecked")
protected static List> extractValuesAsFhirDates(
BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return (List) theChildDefinition.getAccessor().getValues(theElement);
}
protected static List extractValuesAsStrings(
BaseRuntimeChildDefinition theChildDefinition, IBase theValue) {
return theChildDefinition.getAccessor().getValues(theValue).stream()
.map(t -> (IPrimitiveType) t)
.map(IPrimitiveType::getValueAsString)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
}
protected static > String extractSystem(IBaseEnumeration theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
private class ResourceLinkExtractor implements IExtractor {
private PathAndRef myPathAndRef = null;
@Override
public void extract(
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
IBase theValue,
String thePath,
boolean theWantLocalReferences) {
if (theValue instanceof IBaseResource) {
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, (IBaseResource) theValue);
theParams.add(myPathAndRef);
return;
}
String nextType = toRootTypeName(theValue);
switch (nextType) {
case "uri":
case "canonical":
String typeName = toTypeName(theValue);
IPrimitiveType> valuePrimitive = (IPrimitiveType>) theValue;
IBaseReference fakeReference = (IBaseReference)
myContext.getElementDefinition("Reference").newInstance();
fakeReference.setReference(valuePrimitive.getValueAsString());
// Canonical has a root type of "uri"
if ("canonical".equals(typeName)) {
/*
* See #1583
* Technically canonical fields should not allow local references (e.g.
* Questionnaire/123) but it seems reasonable for us to interpret a canonical
* containing a local reference for what it is, and allow people to search
* based on that.
*/
IIdType parsed = fakeReference.getReferenceElement();
if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) {
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, false);
theParams.add(myPathAndRef);
break;
}
if (parsed.isAbsolute()) {
String refValue =
fakeReference.getReferenceElement().getValue();
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true);
theParams.add(myPathAndRef);
/*
* If we have a versioned canonical uri,
* we will index both the version and unversioned uri
* (ie: uri|version and uri)
* This will allow searching to work on both versioned and non-versioned.
*
* HOWEVER
* This doesn't actually fix chained searching (MeasureReport?measure.identifier=...)
*/
if (refValue.contains("|")) {
// extract the non-versioned AND the versioned above so both searches work.
fakeReference = (IBaseReference) myContext
.getElementDefinition("Reference")
.newInstance();
fakeReference.setReference(refValue.substring(0, refValue.indexOf('|')));
}
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true);
theParams.add(myPathAndRef);
}
break;
}
theParams.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)");
break;
case "reference":
case "Reference":
IBaseReference valueRef = (IBaseReference) theValue;
extractResourceLinkFromReference(
theParams, theSearchParam, thePath, theWantLocalReferences, valueRef);
break;
case "CodeableReference":
Optional referenceOpt =
myCodeableReferenceReference.getAccessor().getFirstValueOrNull(theValue);
if (referenceOpt.isPresent()) {
IBaseReference value = (IBaseReference) referenceOpt.get();
extractResourceLinkFromReference(
theParams, theSearchParam, thePath, theWantLocalReferences, value);
}
break;
default:
addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue, thePath);
break;
}
}
private void extractResourceLinkFromReference(
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
String thePath,
boolean theWantLocalReferences,
IBaseReference valueRef) {
IIdType nextId = valueRef.getReferenceElement();
if (nextId.isEmpty() && valueRef.getResource() != null) {
nextId = valueRef.getResource().getIdElement();
}
if (nextId == null || nextId.isEmpty()) {
// Ignore placeholder references that are blank
} else if (!theWantLocalReferences && nextId.getValue().startsWith("#")) {
// Ignore local refs unless we specifically want them
} else {
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false);
theParams.add(myPathAndRef);
}
}
public PathAndRef get(IBase theValue, String thePath) {
extract(
new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null),
theValue,
thePath,
false);
return myPathAndRef;
}
}
private class DateExtractor implements IExtractor {
final String myResourceType;
ResourceIndexedSearchParamDate myIndexedSearchParamDate = null;
public DateExtractor(IBaseResource theResource) {
this(toRootTypeName(theResource));
}
public DateExtractor(String theResourceType) {
myResourceType = theResourceType;
}
@Override
public void extract(
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
IBase theValue,
String thePath,
boolean theWantLocalReferences) {
String nextType = toRootTypeName(theValue);
switch (nextType) {
case "date":
case "dateTime":
case "instant":
addDateTimeTypes(myResourceType, theParams, theSearchParam, theValue);
break;
case "Period":
addDate_Period(myResourceType, theParams, theSearchParam, theValue);
break;
case "Timing":
addDate_Timing(myResourceType, theParams, theSearchParam, theValue);
break;
case "string":
// CarePlan.activitydate can be a string - ignored for now
break;
case "Quantity":
// Condition.onset[x] can have a Quantity - Ignored for now
break;
case "Range":
// Condition.onset[x] can have a Range - Ignored for now
break;
default:
addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue, thePath);
break;
}
}
private void addDate_Period(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
if (start == null) {
start = myStorageSettings.getPeriodIndexStartOfTime().getValue();
startAsString =
myStorageSettings.getPeriodIndexStartOfTime().getValueAsString();
}
if (end == null) {
end = myStorageSettings.getPeriodIndexEndOfTime().getValue();
endAsString = myStorageSettings.getPeriodIndexEndOfTime().getValueAsString();
}
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
start,
startAsString,
end,
endAsString,
startAsString);
theParams.add(myIndexedSearchParamDate);
}
}
private void addDate_Timing(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
List> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
TreeSet dates = new TreeSet<>();
String firstValue = null;
String finalValue = null;
for (IPrimitiveType nextEvent : values) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
finalValue = nextEvent.getValueAsString();
}
}
Optional repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
if (repeat.isPresent()) {
Optional bounds =
myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
if (bounds.isPresent()) {
String boundsType = toRootTypeName(bounds.get());
if ("Period".equals(boundsType)) {
Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
if (start != null) {
dates.add(start);
}
if (end != null) {
dates.add(end);
}
}
}
}
if (!dates.isEmpty()) {
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
dates.first(),
firstValue,
dates.last(),
finalValue,
firstValue);
theParams.add(myIndexedSearchParamDate);
}
}
@SuppressWarnings("unchecked")
private void addDateTimeTypes(
String theResourceType,
Set theParams,
RuntimeSearchParam theSearchParam,
IBase theValue) {
IPrimitiveType nextBaseDateTime = (IPrimitiveType) theValue;
if (nextBaseDateTime.getValue() != null) {
myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(
myPartitionSettings,
theResourceType,
theSearchParam.getName(),
nextBaseDateTime.getValue(),
nextBaseDateTime.getValueAsString(),
nextBaseDateTime.getValue(),
nextBaseDateTime.getValueAsString(),
nextBaseDateTime.getValueAsString());
ourLog.trace("DateExtractor - extracted {} for {}", nextBaseDateTime, theSearchParam.getName());
theParams.add(myIndexedSearchParamDate);
}
}
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) {
extract(
new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null),
theValue,
thePath,
theWantLocalReferences);
return myIndexedSearchParamDate;
}
}
private class TokenExtractor implements IExtractor {
private final String myResourceTypeName;
private final String myUseSystem;
public TokenExtractor(String theResourceTypeName, String theUseSystem) {
myResourceTypeName = theResourceTypeName;
myUseSystem = theUseSystem;
}
@Override
public void extract(
SearchParamSet params,
RuntimeSearchParam searchParam,
IBase value,
String path,
boolean theWantLocalReferences) {
// DSTU3+
if (value instanceof IBaseEnumeration>) {
IBaseEnumeration> obj = (IBaseEnumeration>) value;
String system = extractSystem(obj);
String code = obj.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(
myResourceTypeName, params, searchParam, system, code);
return;
}
// DSTU2 only
if (value instanceof BoundCodeDt) {
BoundCodeDt boundCode = (BoundCodeDt) value;
Enum> valueAsEnum = boundCode.getValueAsEnum();
String system = null;
if (valueAsEnum != null) {
//noinspection unchecked
system = boundCode.getBinder().toSystemString(valueAsEnum);
}
String code = boundCode.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(
myResourceTypeName, params, searchParam, system, code);
return;
}
if (value instanceof IPrimitiveType) {
IPrimitiveType> nextValue = (IPrimitiveType>) value;
String systemAsString = null;
String valueAsString = nextValue.getValueAsString();
if ("CodeSystem.concept.code".equals(path)) {
systemAsString = myUseSystem;
} else if ("ValueSet.codeSystem.concept.code".equals(path)) {
systemAsString = myUseSystem;
}
if (value instanceof IIdType) {
valueAsString = ((IIdType) value).getIdPart();
}
BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(
myResourceTypeName, params, searchParam, systemAsString, valueAsString);
return;
}
switch (path) {
case "Patient.communication":
BaseSearchParamExtractor.this.addToken_PatientCommunication(
myResourceTypeName, params, searchParam, value);
return;
case "Consent.source":
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
return;
case "Location.position":
BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value);
return;
case "StructureDefinition.context":
// TODO: implement this
ourLog.warn("StructureDefinition context indexing not currently supported");
return;
case "CapabilityStatement.rest.security":
BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(
myResourceTypeName, params, searchParam, value);
return;
}
String nextType = BaseSearchParamExtractor.this.toRootTypeName(value);
switch (nextType) {
case "Identifier":
addToken_Identifier(myResourceTypeName, params, searchParam, value);
break;
case "CodeableConcept":
addToken_CodeableConcept(myResourceTypeName, params, searchParam, value);
break;
case "CodeableReference":
addToken_CodeableReference(myResourceTypeName, params, searchParam, value);
break;
case "Coding":
addToken_Coding(myResourceTypeName, params, searchParam, value);
break;
case "ContactPoint":
addToken_ContactPoint(myResourceTypeName, params, searchParam, value);
break;
case "Range":
// Group.characteristic.value[x] can have a Range - Ignored for now
break;
case "Quantity":
// Group.characteristic.value[x] can have a Quantity - Ignored for now
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value, path);
break;
}
}
}
/**
* Extractor that delegates to two other extractors.
*
* @param the type (currently only used for Numeric)
*/
private static class MultiplexExtractor implements IExtractor {
private final IExtractor myExtractor0;
private final IExtractor myExtractor1;
private MultiplexExtractor(IExtractor theExtractor0, IExtractor theExtractor1) {
myExtractor0 = theExtractor0;
myExtractor1 = theExtractor1;
}
@Override
public void extract(
SearchParamSet theParams,
RuntimeSearchParam theSearchParam,
IBase theValue,
String thePath,
boolean theWantLocalReferences) {
myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
}
}
public void setExtractResourceLevelParams(boolean theExtractResourceLevelParams) {
myExtractResourceLevelParams = theExtractResourceLevelParams;
}
}