Work on extractor

This commit is contained in:
James Agnew 2019-10-26 16:56:12 -04:00
parent c455a46160
commit 6580c98ed7
11 changed files with 944 additions and 2809 deletions

View File

@ -43,6 +43,7 @@ import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -354,17 +355,18 @@ public class XmlParser extends BaseParser {
}
if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrative narr = (INarrative) nextChild.getAccessor().getFirstValueOrNull(theElement);
Optional<IBase> narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
INarrativeGenerator gen = myContext.getNarrativeGenerator();
if (gen != null && (narr == null || narr.isEmpty())) {
if (gen != null && narr.isPresent() == false) {
gen.populateResourceNarrative(myContext, theResource);
}
if (narr != null && narr.isEmpty() == false) {
narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
if (narr.isPresent()) {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, narr, childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, narr.get(), childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
continue;
}
}

View File

@ -475,11 +475,10 @@ public class FhirTerser {
* @param thePath The path for the element to be accessed.
* @return A list of values of type {@link Object}.
*/
public List<Object> getValues(IBaseResource theResource, String thePath) {
public List<IBase> getValues(IBaseResource theResource, String thePath) {
Class<IBase> wantedClass = IBase.class;
List values = getValues(theResource, thePath, wantedClass);
return values;
return getValues(theResource, thePath, wantedClass);
}
/**
@ -491,11 +490,10 @@ public class FhirTerser {
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @return A list of values of type {@link Object}.
*/
public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
Class<IBase> wantedClass = IBase.class;
List retVal = getValues(theResource, thePath, wantedClass, theCreate);
return retVal;
return getValues(theResource, thePath, wantedClass, theCreate);
}
/**
@ -508,11 +506,10 @@ public class FhirTerser {
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
* @return A list of values of type {@link Object}.
*/
public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
Class<IBase> wantedClass = IBase.class;
List retVal = getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
return retVal;
return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
}
/**

View File

@ -19,34 +19,34 @@ package ca.uhn.fhir.rest.client.method;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.FhirTerser;
import org.hl7.fhir.instance.model.api.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationMethodBinding.class);
private BundleTypeEnum myBundleType;
private boolean myCanOperateAtInstanceLevel;
private boolean myCanOperateAtServerLevel;
private boolean myCanOperateAtTypeLevel;
private String myDescription;
private final boolean myIdempotent;
private final Integer myIdParamIndex;
@ -63,15 +63,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myBundleType = theBundleType;
myIdempotent = theIdempotent;
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
if (myIdParamIndex != null) {
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
if (next instanceof IdParam) {
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
}
}
} else {
myCanOperateAtTypeLevel = true;
}
Description description = theMethod.getAnnotation(Description.class);
if (description != null) {
@ -113,7 +104,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myOtherOperatiopnType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
}
myReturnParams = new ArrayList<OperationMethodBinding.ReturnType>();
myReturnParams = new ArrayList<>();
if (theReturnParams != null) {
for (OperationParam next : theReturnParams) {
ReturnType type = new ReturnType();
@ -133,13 +124,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
}
if (myIdParamIndex != null) {
myCanOperateAtInstanceLevel = true;
}
if (getResourceName() == null) {
myCanOperateAtServerLevel = true;
}
}
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
@ -169,10 +153,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return myOtherOperatiopnType;
}
public List<ReturnType> getReturnParams() {
return Collections.unmodifiableList(myReturnParams);
}
@Override
public ReturnTypeEnum getReturnType() {
return myReturnType;
@ -197,18 +177,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return createOperationInvocation(getContext(), getResourceName(), id, null, myName, parameters, false);
}
public boolean isCanOperateAtInstanceLevel() {
return this.myCanOperateAtInstanceLevel;
}
public boolean isCanOperateAtServerLevel() {
return this.myCanOperateAtServerLevel;
}
public boolean isCanOperateAtTypeLevel() {
return myCanOperateAtTypeLevel;
}
public boolean isIdempotent() {
return myIdempotent;
}
@ -243,9 +211,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return new HttpPostClientInvocation(theContext, theInput, b.toString());
}
FhirTerser t = theContext.newTerser();
List<Object> parameters = t.getValues(theInput, "Parameters.parameter");
List<IBase> parameters = t.getValues(theInput, "Parameters.parameter");
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
Map<String, List<String>> params = new LinkedHashMap<>();
for (Object nextParameter : parameters) {
IPrimitiveType<?> nextNameDt = (IPrimitiveType<?>) t.getSingleValueOrNull((IBase) nextParameter, "name");
if (nextNameDt == null || nextNameDt.isEmpty()) {
@ -254,7 +222,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
String nextName = nextNameDt.getValueAsString();
if (!params.containsKey(nextName)) {
params.put(nextName, new ArrayList<String>());
params.put(nextName, new ArrayList<>());
}
IBaseDatatype value = (IBaseDatatype) t.getSingleValueOrNull((IBase) nextParameter, "value[x]");

View File

@ -143,14 +143,10 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
}
ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj;
EqualsBuilder b = new EqualsBuilder();
b.append(getResourceType(), obj.getResourceType());
b.append(getParamName(), obj.getParamName());
b.append(getResource(), obj.getResource());
b.append(getSystem(), obj.getSystem());
b.append(getValue(), obj.getValue());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getHashSystem(), obj.getHashSystem());
b.append(getHashSystemAndValue(), obj.getHashSystemAndValue());
b.append(getHashValue(), obj.getHashValue());
return b.isEquals();
}
@ -163,11 +159,6 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
myHashSystem = theHashSystem;
}
private Long getHashIdentity() {
calculateHashes();
return myHashIdentity;
}
private void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
@ -223,8 +214,8 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public int hashCode() {
calculateHashes();
HashCodeBuilder b = new HashCodeBuilder();
b.append(getResourceType());
b.append(getParamName());
b.append(getResource());
b.append(getSystem());
b.append(getValue());
return b.toHashCode();
@ -238,8 +229,8 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append("resourceType", getResourceType());
b.append("paramName", getParamName());
b.append("resourceId", getResourcePid());
b.append("system", getSystem());
b.append("value", getValue());
return b.build();

View File

@ -20,29 +20,34 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.measure.quantity.Quantity;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
public static final Pattern SPLIT = Pattern.compile("\\||( or )");
public static final Pattern SPLIT_R4 = Pattern.compile("\\|");
private static final Pattern ASPLIT = Pattern.compile("\\||( or )");
private static final Pattern ASPLIT_R4 = Pattern.compile("\\|");
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
@Autowired
private FhirContext myContext;
@ -66,20 +71,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return myIgnoredForSearchDatatypes;
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
List<PathAndRef> refs = new ArrayList<PathAndRef>();
String[] nextPathsSplit = theNextSpDef.getPath().split("\\|");
for (String nextPath : nextPathsSplit) {
nextPath = nextPath.trim();
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject != null) {
refs.add(new PathAndRef(nextPath, nextObject));
}
}
}
return refs;
}
/**
* Override parent because we're using FHIRPath here
@ -87,12 +78,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
protected List<IBase> extractValues(String thePaths, IBaseResource theResource) {
List<IBase> values = new ArrayList<>();
if (isNotBlank(thePaths)) {
String[] nextPathsSplit = SPLIT_R4.split(thePaths);
String[] nextPathsSplit = split(thePaths);
for (String nextPath : nextPathsSplit) {
List<? extends IBase> allValues;
Supplier<List<? extends IBase>> allValuesFunc = getPathValueExtractor(theResource, nextPath);
allValues = allValuesFunc.get();
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, e);
}
values.addAll(allValues);
}
@ -109,7 +105,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return values;
}
protected abstract Supplier<List<? extends IBase>> getPathValueExtractor(IBaseResource theResource, String theNextPath);
protected abstract IValueExtractor getPathValueExtractor(IBaseResource theResource, String thePaths);
protected FhirContext getContext() {
return myContext;
@ -141,6 +137,685 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
addIgnoredType(getContext(), "LocationPositionComponent", myIgnoredForSearchDatatypes);
}
private void addQuantity_Quantity(ResourceTable theEntity, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity");
BaseRuntimeChildDefinition quantityValueChild = quantityDefinition.getChildByName("value");
BaseRuntimeChildDefinition quantitySystemChild = quantityDefinition.getChildByName("system");
BaseRuntimeChildDefinition quantityCodeChild = quantityDefinition.getChildByName("code");
Optional<IPrimitiveType<BigDecimal>> valueField = quantityValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = extractValueAsString(quantitySystemChild, theValue);
String code = extractValueAsString(quantityCodeChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theSearchParam.getName(), nextValueValue, system, code);
nextEntity.setResource(theEntity);
theParams.add(nextEntity);
}
}
private void addQuantity_Money(ResourceTable theEntity, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> moneyDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Money");
BaseRuntimeChildDefinition moneyValueChild = moneyDefinition.getChildByName("value");
BaseRuntimeChildDefinition moneyCurrencyChild = moneyDefinition.getChildByName("currency");
Optional<IPrimitiveType<BigDecimal>> valueField = moneyValueChild.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(moneyCurrencyChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theSearchParam.getName(), nextValueValue, nextValueString, nextValueCode);
nextEntity.setResource(theEntity);
theParams.add(nextEntity);
}
}
private void addQuantity_Range(ResourceTable theEntity, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> rangeDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Range");
BaseRuntimeChildDefinition rangeLowValueChild = rangeDefinition.getChildByName("low");
BaseRuntimeChildDefinition rangeHighValueChild = rangeDefinition.getChildByName("high");
Optional<IBase> low = rangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_Quantity(theEntity, theParams, theSearchParam, theIBase));
Optional<IBase> high = rangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_Quantity(theEntity, theParams, theSearchParam, theIBase));
}
private void addToken_Identifier(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> identifierDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Identifier");
BaseRuntimeChildDefinition identifierSystemValueChild = identifierDefinition.getChildByName("system");
BaseRuntimeChildDefinition identifierValueValueChild = identifierDefinition.getChildByName("value");
BaseRuntimeChildDefinition identifierTextValueChild = identifierDefinition.getChildByName("text");
String system = extractValueAsString(identifierSystemValueChild, theValue);
String value = extractValueAsString(identifierValueValueChild, theValue);
if (isNotBlank(value)) {
addTokenIfNotBlank(theParams, theSearchParam, system, value);
}
String text = extractValueAsString(identifierTextValueChild, theValue);
if (isNotBlank(text)) {
addSearchTermIfNotBlank(theParams, theSearchParam, text);
}
}
private void addToken_CodeableConcept(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> codeableConceptDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeableConcept");
BaseRuntimeChildDefinition codeableConceptCodingValueChild = codeableConceptDefinition.getChildByName("coding");
BaseRuntimeChildDefinition codeableConceptTextValueChild = codeableConceptDefinition.getChildByName("text");
List<IBase> codings = codeableConceptCodingValueChild.getAccessor().getValues(theValue);
for (IBase nextCoding : codings) {
addToken_Coding(theParams, theSearchParam, nextCoding);
}
String text = extractValueAsString(codeableConceptTextValueChild, theValue);
if (isNotBlank(text)) {
addSearchTermIfNotBlank(theParams, theSearchParam, text);
}
}
private void addToken_Coding(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> codingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Coding");
BaseRuntimeChildDefinition codingSystemValueChild = codingDefinition.getChildByName("system");
BaseRuntimeChildDefinition codingCodeValueChild = codingDefinition.getChildByName("code");
BaseRuntimeChildDefinition codingDisplayValueChild = codingDefinition.getChildByName("display");
String system = extractValueAsString(codingSystemValueChild, theValue);
String code = extractValueAsString(codingCodeValueChild, theValue);
addTokenIfNotBlank(theParams, theSearchParam, system, code);
String text = extractValueAsString(codingDisplayValueChild, theValue);
addSearchTermIfNotBlank(theParams, theSearchParam, text);
}
private void addToken_ContactPoint(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint");
BaseRuntimeChildDefinition contactPointSystemValueChild = contactPointDefinition.getChildByName("system");
BaseRuntimeChildDefinition contactPointValueValueChild = contactPointDefinition.getChildByName("value");
String system = extractValueAsString(contactPointSystemValueChild, theValue);
String value = extractValueAsString(contactPointValueValueChild, theValue);
addTokenIfNotBlank(theParams, theSearchParam, system, value);
}
private void addToken_PatientCommunication(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> patientDefinition = getContext().getResourceDefinition("Patient");
BaseRuntimeChildDefinition patientCommunicationValueChild = patientDefinition.getChildByName("communication");
BaseRuntimeElementCompositeDefinition<?> patientCommunicationDefinition = (BaseRuntimeElementCompositeDefinition<?>) patientCommunicationValueChild.getChildByName("communication");
BaseRuntimeChildDefinition patientCommunicationLanguageValueChild = patientCommunicationDefinition.getChildByName("language");
List<IBase> values = patientCommunicationLanguageValueChild.getAccessor().getValues(theValue);
for (IBase next : values) {
addToken_CodeableConcept(theParams, theSearchParam, next);
}
}
private void addToken_CapabilityStatementRestSecurity(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
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");
BaseRuntimeChildDefinition capabilityStatementRestSecurityServiceValueChild = capabilityStatementRestSecurityDefinition.getChildByName("service");
List<IBase> values = capabilityStatementRestSecurityServiceValueChild.getAccessor().getValues(theValue);
for (IBase nextValue : values) {
addToken_CodeableConcept(theParams, theSearchParam, nextValue);
}
}
private void addDate_Period(Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> periodDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Period");
BaseRuntimeChildDefinition periodStartValueChild = periodDefinition.getChildByName("start");
BaseRuntimeChildDefinition periodEndValueChild = periodDefinition.getChildByName("end");
Date start = extractValueAsDate(periodStartValueChild, theValue);
String startAsString = extractValueAsString(periodStartValueChild, theValue);
Date end = extractValueAsDate(periodEndValueChild, theValue);
if (start != null || end != null) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theSearchParam.getName(), start, end, startAsString);
theParams.add(nextEntity);
}
}
private void addDate_Timing(Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> timingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Timing");
BaseRuntimeChildDefinition timingEventValueChild = timingDefinition.getChildByName("event");
BaseRuntimeChildDefinition timingRepeatValueChild = timingDefinition.getChildByName("repeat");
BaseRuntimeElementCompositeDefinition<?> timingRepeatDefinition = (BaseRuntimeElementCompositeDefinition<?>) timingRepeatValueChild.getChildByName("repeat");
BaseRuntimeChildDefinition timingRepeatBoundsValueChild = timingRepeatDefinition.getChildByName("bounds");
List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(timingEventValueChild, theValue);
TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
for (IPrimitiveType<Date> nextEvent : values) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
}
}
Optional<IBase> repeat = timingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
if (repeat.isPresent()) {
Optional<IBase> bounds = timingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
if (bounds.isPresent()) {
String boundsType = toTypeName(bounds.get());
switch (boundsType) {
case "Period":
BaseRuntimeElementCompositeDefinition<?> periodDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Period");
BaseRuntimeChildDefinition periodStartValueChild = periodDefinition.getChildByName("start");
BaseRuntimeChildDefinition periodEndValueChild = periodDefinition.getChildByName("end");
Date start = extractValueAsDate(periodStartValueChild, bounds.get());
Date end = extractValueAsDate(periodEndValueChild, bounds.get());
dates.add(start);
dates.add(end);
break;
}
}
}
if (!dates.isEmpty()) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theSearchParam.getName(), dates.first(), dates.last(), firstValue);
theParams.add(nextEntity);
}
}
private void addNumber_Duration(Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> durationDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Duration");
BaseRuntimeChildDefinition durationSystemValueChild = durationDefinition.getChildByName("system");
BaseRuntimeChildDefinition durationCodeValueChild = durationDefinition.getChildByName("code");
BaseRuntimeChildDefinition durationValueValueChild = durationDefinition.getChildByName("value");
String system = extractValueAsString(durationSystemValueChild, theValue);
String code = extractValueAsString(durationCodeValueChild, theValue);
BigDecimal value = extractValueAsBigDecimal(durationValueValueChild, theValue);
if (value != null) {
if (SearchParamConstants.UCUM_NS.equals(system)) {
if (isNotBlank(code)) {
Unit<? extends Quantity> unit = Unit.valueOf(code);
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(value.doubleValue());
value = new BigDecimal(dayValue);
}
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
private void addString_HumanName(Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> humanNameDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("HumanName");
BaseRuntimeChildDefinition humanNameFamilyValueChild = humanNameDefinition.getChildByName("family");
BaseRuntimeChildDefinition humanNameGivenValueChild = humanNameDefinition.getChildByName("given");
List<String> families = extractValuesAsStrings(humanNameFamilyValueChild, theValue);
for (String next : families) {
addSearchTermIfNotBlank(theParams, theSearchParam, next);
}
List<String> givens = extractValuesAsStrings(humanNameGivenValueChild, theValue);
for (String next : givens) {
addSearchTermIfNotBlank(theParams, theSearchParam, next);
}
}
private void addString_Quantity(Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity");
BaseRuntimeChildDefinition quantityValueChild = quantityDefinition.getChildByName("value");
BigDecimal value = extractValueAsBigDecimal(quantityValueChild, theValue);
if (value != null) {
addSearchTermIfNotBlank(theParams, theSearchParam, value.toPlainString());
}
}
private void addString_Range(Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> rangeDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Range");
BaseRuntimeChildDefinition rangeLowValueChild = rangeDefinition.getChildByName("low");
BigDecimal value = extractValueAsBigDecimal(rangeLowValueChild, theValue);
if (value != null) {
addSearchTermIfNotBlank(theParams, theSearchParam, value.toPlainString());
}
}
private void addString_ContactPoint(Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint");
BaseRuntimeChildDefinition contactPointValueValueChild = contactPointDefinition.getChildByName("value");
String value = extractValueAsString(contactPointValueValueChild, theValue);
if (isNotBlank(value)) {
addSearchTermIfNotBlank(theParams, theSearchParam, value);
}
}
private void addString_Address(Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> addressDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Address");
BaseRuntimeChildDefinition addressLineValueChild = addressDefinition.getChildByName("line");
BaseRuntimeChildDefinition addressCityValueChild = addressDefinition.getChildByName("city");
BaseRuntimeChildDefinition addressStateValueChild = addressDefinition.getChildByName("state");
BaseRuntimeChildDefinition addressCountryValueChild = addressDefinition.getChildByName("country");
BaseRuntimeChildDefinition addressPostalCodeValueChild = addressDefinition.getChildByName("postalCode");
List<String> allNames = new ArrayList<>(extractValuesAsStrings(addressLineValueChild, theValue));
String city = extractValueAsString(addressCityValueChild, theValue);
if (isNotBlank(city)) {
allNames.add(city);
}
String state = extractValueAsString(addressStateValueChild, theValue);
if (isNotBlank(state)) {
allNames.add(state);
}
String country = extractValueAsString(addressCountryValueChild, theValue);
if (isNotBlank(country)) {
allNames.add(country);
}
String postalCode = extractValueAsString(addressPostalCodeValueChild, theValue);
if (isNotBlank(postalCode)) {
allNames.add(postalCode);
}
for (String nextName : allNames) {
addSearchTermIfNotBlank(theParams, theSearchParam, nextName);
}
}
private void addNumber_Quantity(Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Duration");
BaseRuntimeChildDefinition quantityValueValueChild = quantityDefinition.getChildByName("value");
BigDecimal value = extractValueAsBigDecimal(quantityValueValueChild, theValue);
if (value != null) {
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theSearchParam.getName(), value);
theParams.add(nextEntity);
}
}
@SuppressWarnings("unchecked")
private void addNumber_Integer(Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = new BigDecimal(value.getValue());
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
}
@SuppressWarnings("unchecked")
private void addNumber_Decimal(Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theValue;
if (value.getValue() != null) {
BigDecimal valueDecimal = value.getValue();
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theSearchParam.getName(), valueDecimal);
theParams.add(nextEntity);
}
}
@Override
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
// TODO: implement
return Collections.emptySet();
}
@Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamDate> extractor = (params, searchParam, value, path) -> {
String nextType = toTypeName(value);
switch (nextType) {
case "date":
case "dateTime":
case "instant":
addDateTimeTypes(params, searchParam, value);
break;
case "Period":
addDate_Period(params, searchParam, value);
break;
case "Timing":
addDate_Timing(params, searchParam, value);
break;
case "string":
// CarePlan.activitydate can be a string - ignored for now
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE);
}
private <T extends BaseResourceIndexedSearchParam> Set<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType) {
Set<T> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != theSearchParamType) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (IBase nextObject : extractValues(nextPath, theResource)) {
if (nextObject != null) {
theExtractor.extract(retVal, nextSpDef, nextObject, nextPath);
}
}
}
return retVal;
}
private String toTypeName(IBase nextObject) {
return getContext().getElementDefinition(nextObject.getClass()).getName();
}
@SuppressWarnings("unchecked")
private void addDateTimeTypes(Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
if (nextBaseDateTime.getValue() != null) {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString());
theParams.add(param);
}
}
@Override
public Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamNumber> extractor = (params, searchParam, value, path) -> {
String nextType = toTypeName(value);
switch (nextType) {
case "Duration":
addNumber_Duration(params, searchParam, value);
break;
case "Quantity":
addNumber_Quantity(params, searchParam, value);
break;
case "integer":
addNumber_Integer(params, searchParam, value);
break;
case "decimal":
addNumber_Decimal(params, searchParam, value);
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER);
}
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamQuantity> extractor = (params, searchParam, value, path) -> {
String nextType = toTypeName(value);
switch (nextType) {
case "Quantity":
addQuantity_Quantity(theEntity, params, searchParam, value);
break;
case "Money":
addQuantity_Money(theEntity, params, searchParam, value);
break;
case "Range":
addQuantity_Range(theEntity, params, searchParam, value);
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamString> extractor = (params, searchParam, value, path) -> {
if (value instanceof IPrimitiveType) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value;
String valueAsString = nextValue.getValueAsString();
addSearchTermIfNotBlank(params, searchParam, valueAsString);
return;
}
String nextType = toTypeName(value);
switch (nextType) {
case "HumanName":
addString_HumanName(params, searchParam, value);
break;
case "Address":
addString_Address(params, searchParam, value);
break;
case "ContactPoint":
addString_ContactPoint(params, searchParam, value);
break;
case "Quantity":
addString_Quantity(params, searchParam, value);
break;
case "Range":
addString_Range(params, searchParam, value);
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING);
}
private void addUri_Uri(Set<ResourceIndexedSearchParamUri> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<?> value = (IPrimitiveType<?>) theValue;
String valueAsString = value.getValueAsString();
if (isNotBlank(valueAsString)) {
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(theSearchParam.getName(), valueAsString);
theParams.add(nextEntity);
}
}
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
BaseRuntimeElementCompositeDefinition<?> codeSystemDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeSystem");
assert codeSystemDefinition != null;
BaseRuntimeChildDefinition codeSystemUrlValueChild = codeSystemDefinition.getChildByName("url");
String resourceTypeName = toTypeName(theResource);
String useSystem;
if (resourceTypeName.equals("CodeSystem")) {
useSystem = extractValueAsString(codeSystemUrlValueChild, theResource);
} else {
useSystem = null;
}
IExtractor<BaseResourceIndexedSearchParam> extractor = (params, searchParam, value, path) -> {
if (value instanceof IBaseEnumeration<?>) {
IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value;
String system = extractSystem(obj);
String code = obj.getValueAsString();
addTokenIfNotBlank(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 = useSystem;
}
addTokenIfNotBlank(params, searchParam, systemAsString, valueAsString);
return;
}
switch (path) {
case "Patient.communication":
addToken_PatientCommunication(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":
ourLog.warn("Position search not currently supported, not indexing location");
return;
case "StructureDefinition.context":
// TODO: implement this
ourLog.warn("StructureDefinition context indexing not currently supported");
return;
case "CapabilityStatement.rest.security":
addToken_CapabilityStatementRestSecurity(params, searchParam, value);
return;
}
String nextType = toTypeName(value);
switch (nextType) {
case "Identifier":
addToken_Identifier(params, searchParam, value);
break;
case "CodeableConcept":
addToken_CodeableConcept(params, searchParam, value);
break;
case "Coding":
addToken_Coding(params, searchParam, value);
break;
case "ContactPoint":
addToken_ContactPoint(params, searchParam, value);
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
}
private void addTokenIfNotBlank(Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
String system = theSystem;
String value = theValue;
if (isNotBlank(system) || isNotBlank(value)) {
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
if (value != null && value.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(theSearchParam.getName(), system, value);
theParams.add(nextEntity);
}
}
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamUri> extractor = (params, searchParam, value, path) -> {
String nextType = toTypeName(value);
switch (nextType) {
case "uri":
case "url":
addUri_Uri(params, searchParam, value);
break;
default:
throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass());
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI);
}
@SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
private void addSearchTermIfNotBlank(Set<? extends BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theValue) {
if (isNotBlank(theValue)) {
if (theValue.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
theValue = theValue.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
String searchParamName = theSearchParam.getName();
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), searchParamName, StringNormalizer.normalizeString(theValue), theValue);
Set params = theParams;
params.add(nextEntity);
}
}
private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), nextSpDef.getName(), StringNormalizer.normalizeString(value), value);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
protected String[] split(String thePaths) {
if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
if (!thePaths.contains("|")) {
return new String[]{thePaths};
}
return ASPLIT_R4.split(thePaths);
} else {
if (!thePaths.contains("|") && !thePaths.contains(" or ")) {
return new String[]{thePaths};
}
return ASPLIT.split(thePaths);
}
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
ArrayList<PathAndRef> retVal = new ArrayList<>();
String[] nextPathsSplit = split(theNextSpDef.getPath());
for (String path : nextPathsSplit) {
path = path.trim();
if (isNotBlank(path)) {
for (Object next : extractValues(path, theResource)) {
retVal.add(new PathAndRef(path, next));
}
}
}
return retVal;
}
@FunctionalInterface
public interface IValueExtractor {
List<? extends IBase> get() throws FHIRException;
}
@FunctionalInterface
private interface IExtractor<T> {
void extract(Set<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath);
}
private static void addIgnoredType(FhirContext theCtx, String theType, Set<Class<?>> theIgnoredTypes) {
BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
@ -148,5 +823,62 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
private static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<?>>getFirstValueOrNull(theElement)
.map(t -> t.getValueAsString())
.orElse(null);
}
private static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<Date>>getFirstValueOrNull(theElement)
.map(t -> t.getValue())
.orElse(null);
}
private static Integer extractValueAsInteger(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<Integer>>getFirstValueOrNull(theElement)
.map(t -> t.getValue())
.orElse(null);
}
private static BigDecimal extractValueAsBigDecimal(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement)
.map(t -> t.getValue())
.orElse(null);
}
@SuppressWarnings("unchecked")
private static List<IPrimitiveType<Date>> extractValuesAsFhirDates(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return (List) theChildDefinition
.getAccessor()
.getValues(theElement);
}
private static List<String> extractValuesAsStrings(BaseRuntimeChildDefinition theChildDefinition, IBase theValue) {
return theChildDefinition
.getAccessor()
.getValues(theValue)
.stream()
.map(t -> (IPrimitiveType) t)
.map(t -> t.getValueAsString())
.filter(t -> isNotBlank(t))
.collect(Collectors.toList());
}
private static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
}

View File

@ -20,656 +20,40 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.base.composite.BaseHumanNameDt;
import ca.uhn.fhir.model.dstu2.composite.*;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.resource.Patient.Communication;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum;
import ca.uhn.fhir.model.primitive.*;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.measure.quantity.Quantity;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.math.BigDecimal;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.List;
public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorDstu2.class);
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
if (isBlank(searchTerm)) {
return;
}
if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), resourceName, StringNormalizer.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), nextSpDef.getName(), StringNormalizer.normalizeString(value), value);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
@Override
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
// TODO: implement
return Collections.emptySet();
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable,
* ca.uhn.fhir.model.api.IResource)
*/
@Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
ResourceIndexedSearchParamDate nextEntity;
if (nextObject instanceof BaseDateTimeDt) {
BaseDateTimeDt nextValue = (BaseDateTimeDt) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof PeriodDt) {
PeriodDt nextValue = (PeriodDt) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
if (nextEntity != null) {
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable,
* ca.uhn.fhir.model.api.IResource)
*/
@Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
if (nextObject instanceof DurationDt) {
DurationDt nextValue = (DurationDt) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
if (new UriDt(SearchParamConstants.UCUM_NS).equals(nextValue.getSystemElement())) {
if (isNotBlank(nextValue.getCode())) {
Unit<? extends Quantity> unit = Unit.valueOf(nextValue.getCode());
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
DurationDt newValue = new DurationDt();
newValue.setSystem(SearchParamConstants.UCUM_NS);
newValue.setCode(NonSI.DAY.toString());
newValue.setValue(dayValue);
nextValue = newValue;
/*
* @SuppressWarnings("unchecked") PhysicsUnit<? extends
* org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends
* org.unitsofmeasurement.quantity.Quantity<?>>)
* UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if
* (unit.isCompatible(UCUM.DAY)) {
*
* @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit =
* (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY); double
* dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); DurationDt newValue =
* new DurationDt(); newValue.setSystem(UCUM_NS); newValue.setCode(UCUM.DAY.getSymbol());
* newValue.setValue(dayValue); nextValue=newValue; }
*/
}
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof QuantityDt) {
QuantityDt nextValue = (QuantityDt) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof IntegerDt) {
IntegerDt nextValue = (IntegerDt) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof DecimalDt) {
DecimalDt nextValue = (DecimalDt) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable,
* ca.uhn.fhir.model.api.IResource)
*/
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
if (nextObject instanceof QuantityDt) {
QuantityDt nextValue = (QuantityDt) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValue.getValueElement().getValue(), nextValue.getSystemElement().getValueAsString(), nextValue.getCode());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable,
* ca.uhn.fhir.model.api.IResource)
*/
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
String resourceName = getContext().getResourceDefinition(theResource).getName();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue;
}
String nextPath = nextSpDef.getPath();
String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) {
// TODO: implement phonetic, and any others that have no path
if ("Questionnaire".equals(resourceName) && nextSpDef.getName().equals("title")) {
Questionnaire q = (Questionnaire) theResource;
String title = q.getGroup().getTitle();
addSearchTerm(theEntity, retVal, nextSpName, title);
}
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
if (nextObject instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject;
String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else {
if (nextObject instanceof BaseHumanNameDt) {
ArrayList<StringDt> allNames = new ArrayList<StringDt>();
HumanNameDt nextHumanName = (HumanNameDt) nextObject;
allNames.addAll(nextHumanName.getFamily());
allNames.addAll(nextHumanName.getGiven());
for (StringDt nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof AddressDt) {
ArrayList<StringDt> allNames = new ArrayList<StringDt>();
AddressDt nextAddress = (AddressDt) nextObject;
allNames.addAll(nextAddress.getLine());
allNames.add(nextAddress.getCityElement());
allNames.add(nextAddress.getStateElement());
allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement());
for (StringDt nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof ContactPointDt) {
ContactPointDt nextContact = (ContactPointDt) nextObject;
if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
protected IValueExtractor getPathValueExtractor(IBaseResource theResource, String thePaths) {
return () -> {
List<IBase> values = new ArrayList<>();
String[] nextPathsSplit = split(thePaths);
FhirTerser t = getContext().newTerser();
for (String nextPath : nextPathsSplit) {
String nextPathTrimmed = nextPath.trim();
List<IBase> allValues = t.getValues(theResource, nextPathTrimmed);
for (IBase next : allValues) {
if (next instanceof IBaseExtension) {
IBaseDatatype value = ((IBaseExtension) next).getValue();
if (value != null) {
values.add(value);
}
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
}
values.add(next);
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable,
* ca.uhn.fhir.model.api.IResource)
*/
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>();
String useSystem = null;
if (theResource instanceof ValueSet) {
ValueSet vs = (ValueSet) theResource;
useSystem = vs.getCodeSystem().getSystem();
}
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
List<String> systems = new ArrayList<String>();
List<String> codes = new ArrayList<String>();
String needContactPointSystem = null;
if (nextPath.endsWith("(system=phone)")) {
nextPath = nextPath.substring(0, nextPath.length() - "(system=phone)".length());
needContactPointSystem = "phone";
}
if (nextPath.endsWith("(system=email)")) {
nextPath = nextPath.substring(0, nextPath.length() - "(system=email)".length());
needContactPointSystem = "email";
}
for (Object nextObject : extractValues(nextPath, theResource)) {
// Patient:language
if (nextObject instanceof Patient.Communication) {
Communication nextValue = (Patient.Communication) nextObject;
nextObject = nextValue.getLanguage();
}
if (nextObject instanceof IdentifierDt) {
IdentifierDt nextValue = (IdentifierDt) nextObject;
if (nextValue.isEmpty()) {
continue;
}
String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
String value = nextValue.getValueElement().getValue();
if (isNotBlank(value)) {
systems.add(system);
codes.add(value);
}
if (isNotBlank(nextValue.getType().getText())) {
addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
}
} else if (nextObject instanceof ContactPointDt) {
ContactPointDt nextValue = (ContactPointDt) nextObject;
if (nextValue.isEmpty()) {
continue;
}
if (isNotBlank(needContactPointSystem)) {
if (!needContactPointSystem.equals(nextValue.getSystemElement().getValueAsString())) {
continue;
}
}
systems.add(nextValue.getSystemElement().getValueAsString());
codes.add(nextValue.getValueElement().getValue());
} else if (nextObject instanceof BoundCodeDt) {
BoundCodeDt<?> obj = (BoundCodeDt<?>) nextObject;
String system = extractSystem(obj);
String code = obj.getValue();
if (isNotBlank(code)) {
systems.add(system);
codes.add(code);
}
} else if (nextObject instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject;
if (nextValue.isEmpty()) {
continue;
}
if ("ValueSet.codeSystem.concept.code".equals(nextPath)) {
systems.add(useSystem);
} else {
systems.add(null);
}
codes.add(nextValue.getValueAsString());
} else if (nextObject instanceof CodingDt) {
CodingDt nextValue = (CodingDt) nextObject;
extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
} else if (nextObject instanceof CodeableConceptDt) {
CodeableConceptDt nextCC = (CodeableConceptDt) nextObject;
if (!nextCC.getTextElement().isEmpty()) {
addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
}
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
} else if (nextObject instanceof RestSecurity) {
// Conformance.security search param points to something kind of useless right now - This should probably
// be fixed.
RestSecurity sec = (RestSecurity) nextObject;
for (BoundCodeableConceptDt<RestfulSecurityServiceEnum> nextCC : sec.getService()) {
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
}
} else if (nextObject instanceof Location.Position) {
ourLog.warn("Position search not currently supported, not indexing location");
continue;
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
}
assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
Set<Pair<String, String>> haveValues = new HashSet<Pair<String, String>>();
for (int i = 0; i < systems.size(); i++) {
String system = systems.get(i);
String code = codes.get(i);
if (isBlank(system) && isBlank(code)) {
continue;
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
Pair<String, String> nextPair = Pair.of(system, code);
if (haveValues.contains(nextPair)) {
continue;
}
haveValues.add(nextPair);
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
if (nextObject instanceof UriDt) {
UriDt nextValue = (UriDt) nextObject;
if (isBlank(nextValue.getValue())) {
continue;
}
ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
} else {
continue;
}
}
}
}
return retVal;
}
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
for (CodingDt nextCoding : theCodeableConcept.getCoding()) {
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
}
}
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef, CodingDt nextCoding) {
if (nextCoding != null && !nextCoding.isEmpty()) {
String nextSystem = nextCoding.getSystemElement().getValueAsString();
String nextCode = nextCoding.getCodeElement().getValue();
if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
theSystems.add(nextSystem);
theCodes.add(nextCode);
}
if (!nextCoding.getDisplayElement().isEmpty()) {
addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
}
}
}
@Override
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
List<Object> values = new ArrayList<>();
String[] nextPathsSplit = SPLIT.split(thePaths);
FhirTerser t = getContext().newTerser();
for (String nextPath : nextPathsSplit) {
String nextPathTrimmed = nextPath.trim();
List<Object> allValues;
try {
allValues = t.getValues(theResource, nextPathTrimmed);
} catch (Exception e) {
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
throw new InternalErrorException(msg, e);
}
for (Object next : allValues) {
if (next instanceof IBaseExtension) {
IBaseDatatype value = ((IBaseExtension) next).getValue();
if (value != null) {
values.add(value);
}
} else {
values.add(next);
}
}
}
return values;
}
private static <T extends Enum<?>> String extractSystem(BoundCodeDt<T> theBoundCode) {
if (theBoundCode.getValueAsEnum() != null) {
IValueSetEnumBinder<T> binder = theBoundCode.getBinder();
return binder.toSystemString(theBoundCode.getValueAsEnum());
}
return null;
return values;
};
}
}

View File

@ -20,48 +20,31 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.dstu3.model.Location.LocationPositionComponent;
import org.hl7.fhir.dstu3.model.Patient.PatientCommunicationComponent;
import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.math.BigDecimal;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.trim;
public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorDstu3.class);
@Autowired
private org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport myValidationSupport;
private HapiWorkerContext myWorkerContext;
private FHIRPathEngine myFhirPathEngine;
/**
* Constructor
@ -77,653 +60,27 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
myValidationSupport = theValidationSupport;
}
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
if (!nextValue.getValueElement().isEmpty()) {
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
String nextValueString = nextValue.getSystemElement().getValueAsString();
String nextValueCode = nextValue.getCode();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
if (isBlank(searchTerm)) {
return;
}
if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), resourceName, StringNormalizer.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), nextSpDef.getName(), StringNormalizer.normalizeString(value), value);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
for (String path : nextPathsSplit) {
path = path.trim();
if (isNotBlank(path)) {
for (Object next : extractValues(path, theResource)) {
retVal.add(new PathAndRef(path, next));
}
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
// TODO: implement
return Collections.emptySet();
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
String resourceType = theEntity.getResourceType();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
ResourceIndexedSearchParamDate nextEntity;
if (nextObject instanceof BaseDateTimeType) {
BaseDateTimeType nextValue = (BaseDateTimeType) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) {
continue;
}
String firstValue = null;
TreeSet<Date> dates = new TreeSet<>();
for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
}
}
if (nextValue.getRepeat().hasBounds()) {
if (nextValue.getRepeat().getBoundsPeriod().getStart() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getStart());
}
if (nextValue.getRepeat().getBoundsPeriod().getEnd() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getEnd());
}
}
if (dates.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
} else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string
continue;
} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
}
if (nextEntity != null) {
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof Duration) {
Duration nextValue = (Duration) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
if (SearchParamConstants.UCUM_NS.equals(nextValue.getSystem())) {
if (isNotBlank(nextValue.getCode())) {
Unit<? extends javax.measure.quantity.Quantity> unit = Unit.valueOf(nextValue.getCode());
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
Duration newValue = new Duration();
newValue.setSystem(SearchParamConstants.UCUM_NS);
newValue.setCode(NonSI.DAY.toString());
newValue.setValue(dayValue);
nextValue = newValue;
/*
* @SuppressWarnings("unchecked") PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>>)
* UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if (unit.isCompatible(UCUM.DAY)) {
*
* @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY);
* double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); Duration newValue = new Duration(); newValue.setSystem(UCUM_NS);
* newValue.setCode(UCUM.DAY.getSymbol()); newValue.setValue(dayValue); nextValue=newValue; }
*/
}
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof Quantity) {
Quantity nextValue = (Quantity) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof IntegerType) {
IntegerType nextValue = (IntegerType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof DecimalType) {
DecimalType nextValue = (DecimalType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof Quantity) {
Quantity nextValue = (Quantity) nextObject;
addQuantity(theEntity, retVal, resourceName, nextValue);
} else if (nextObject instanceof Range) {
Range nextValue = (Range) nextObject;
addQuantity(theEntity, retVal, resourceName, nextValue.getLow());
addQuantity(theEntity, retVal, resourceName, nextValue.getHigh());
} else if (nextObject instanceof LocationPositionComponent) {
continue;
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
String resourceName = getContext().getResourceDefinition(theResource).getName();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue;
}
String nextPath = nextSpDef.getPath();
String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) {
// // TODO: implement phonetic, and any others that have no path
//
// // TODO: do we still need this check?
// if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) {
// Questionnaire q = (Questionnaire) theResource;
// String title = "";// q.getGroup().getTitle();
// addSearchTerm(theEntity, retVal, nextSpName, title);
// }
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else {
if (nextObject instanceof HumanName) {
ArrayList<StringType> allNames = new ArrayList<>();
HumanName nextHumanName = (HumanName) nextObject;
if (isNotBlank(nextHumanName.getFamily())) {
allNames.add(nextHumanName.getFamilyElement());
}
allNames.addAll(nextHumanName.getGiven());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof Address) {
ArrayList<StringType> allNames = new ArrayList<StringType>();
Address nextAddress = (Address) nextObject;
allNames.addAll(nextAddress.getLine());
allNames.add(nextAddress.getCityElement());
allNames.add(nextAddress.getStateElement());
allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextContact = (ContactPoint) nextObject;
if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
}
} else if (nextObject instanceof Quantity) {
BigDecimal value = ((Quantity) nextObject).getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
} else if (nextObject instanceof Range) {
SimpleQuantity low = ((Range) nextObject).getLow();
if (low != null) {
BigDecimal value = low.getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
}
} else {
throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>();
String useSystem = null;
if (theResource instanceof CodeSystem) {
CodeSystem cs = (CodeSystem) theResource;
useSystem = cs.getUrl();
}
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
List<String> systems = new ArrayList<>();
List<String> codes = new ArrayList<>();
// String needContactPointSystem = null;
// if (nextPath.contains(".where(system='phone')")) {
// nextPath = nextPath.replace(".where(system='phone')", "");
// needContactPointSystem = "phone";
// }
// if (nextPath.contains(".where(system='email')")) {
// nextPath = nextPath.replace(".where(system='email')", "");
// needContactPointSystem = "email";
// }
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
// Patient:language
if (nextObject instanceof PatientCommunicationComponent) {
PatientCommunicationComponent nextValue = (PatientCommunicationComponent) nextObject;
nextObject = nextValue.getLanguage();
}
if (nextObject instanceof Identifier) {
Identifier nextValue = (Identifier) nextObject;
if (nextValue.isEmpty()) {
continue;
}
String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
String value = nextValue.getValueElement().getValue();
if (isNotBlank(value)) {
systems.add(system);
codes.add(value);
}
if (isNotBlank(nextValue.getType().getText())) {
addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextValue = (ContactPoint) nextObject;
if (nextValue.isEmpty()) {
continue;
}
systems.add(nextValue.getSystemElement().getValueAsString());
codes.add(nextValue.getValueElement().getValue());
} else if (nextObject instanceof Enumeration<?>) {
Enumeration<?> obj = (Enumeration<?>) nextObject;
String system = extractSystem(obj);
String code = obj.getValueAsString();
if (isNotBlank(code)) {
systems.add(system);
codes.add(code);
}
} else if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
if (nextValue.isEmpty()) {
continue;
}
if ("CodeSystem.concept.code".equals(nextPath)) {
systems.add(useSystem);
} else {
systems.add(null);
}
codes.add(nextValue.getValueAsString());
} else if (nextObject instanceof Coding) {
Coding nextValue = (Coding) nextObject;
extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
} else if (nextObject instanceof CodeableConcept) {
CodeableConcept nextCC = (CodeableConcept) nextObject;
if (!nextCC.getTextElement().isEmpty()) {
addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
}
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
} else if (nextObject instanceof CapabilityStatementRestSecurityComponent) {
// Conformance.security search param points to something kind of useless right now - This should probably
// be fixed.
CapabilityStatementRestSecurityComponent sec = (CapabilityStatementRestSecurityComponent) nextObject;
for (CodeableConcept nextCC : sec.getService()) {
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
}
} else if (nextObject instanceof LocationPositionComponent) {
ourLog.warn("Position search not currently supported, not indexing location");
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
protected IValueExtractor getPathValueExtractor(IBaseResource theResource, String thePaths) {
return () -> {
List<IBase> values = new ArrayList<>();
String[] nextPathsSplit = split(thePaths);
for (String nextPath : nextPathsSplit) {
List<Base> allValues = myFhirPathEngine.evaluate((Base) theResource, trim(nextPath));
if (allValues.isEmpty() == false) {
values.addAll(allValues);
}
}
assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
Set<Pair<String, String>> haveValues = new HashSet<Pair<String, String>>();
for (int i = 0; i < systems.size(); i++) {
String system = systems.get(i);
String code = codes.get(i);
if (isBlank(system) && isBlank(code)) {
continue;
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
Pair<String, String> nextPair = Pair.of(system, code);
if (haveValues.contains(nextPair)) {
continue;
}
haveValues.add(nextPair);
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof UriType) {
UriType nextValue = (UriType) nextObject;
if (isBlank(nextValue.getValue())) {
continue;
}
ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
for (Coding nextCoding : theCodeableConcept.getCoding()) {
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
}
}
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
RuntimeSearchParam theParameterDef, Coding nextCoding) {
if (nextCoding != null && !nextCoding.isEmpty()) {
String nextSystem = nextCoding.getSystemElement().getValueAsString();
String nextCode = nextCoding.getCodeElement().getValue();
if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
theSystems.add(nextSystem);
theCodes.add(nextCode);
}
if (!nextCoding.getDisplayElement().isEmpty()) {
addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
}
}
}
/**
* Override parent because we're using FHIRPath here
*/
@Override
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
FHIRPathEngine fp = new FHIRPathEngine(myWorkerContext);
List<Object> values = new ArrayList<>();
String[] nextPathsSplit = SPLIT.split(thePaths);
for (String nextPath : nextPathsSplit) {
List<Base> allValues;
try {
allValues = fp.evaluate((Base) theResource, trim(nextPath));
} catch (FHIRException e) {
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
throw new InternalErrorException(msg, e);
}
if (allValues.isEmpty() == false) {
values.addAll(allValues);
}
}
for (int i = 0; i < values.size(); i++) {
Object nextObject = values.get(i);
if (nextObject instanceof Extension) {
Extension nextExtension = (Extension) nextObject;
nextObject = nextExtension.getValue();
values.set(i, nextObject);
}
}
return values;
}
@VisibleForTesting
void setValidationSupportForTesting(org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
return values;
};
}
@Override
@PostConstruct
public void start() {
myWorkerContext = new HapiWorkerContext(getContext(), myValidationSupport);
}
private static <T extends Enum<?>> String extractSystem(Enumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
myFhirPathEngine = new FHIRPathEngine(myWorkerContext);
}
}

View File

@ -20,47 +20,30 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.r4.model.Location.LocationPositionComponent;
import org.hl7.fhir.r4.model.Patient.PatientCommunicationComponent;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.math.BigDecimal;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorR4.class);
private static final Set<Class<?>> ourIgnoredForSearchDatatypes;
static {
@ -98,6 +81,20 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
initFhirPath();
}
@Override
protected IValueExtractor getPathValueExtractor(IBaseResource theResource, String thePaths) {
return () -> {
List<IBase> values = new ArrayList<>();
String[] nextPathsSplit = split(thePaths);
for (String nextPath : nextPathsSplit) {
List<Base> allValues = myFhirPathEngine.evaluate((Base) theResource, nextPath);
values.addAll(allValues);
}
return values;
};
}
@PostConstruct
public void initFhirPath() {
IWorkerContext worker = new HapiWorkerContext(getContext(), myValidationSupport);
@ -105,648 +102,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices());
}
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
if (!nextValue.getValueElement().isEmpty()) {
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
String nextValueString = nextValue.getSystemElement().getValueAsString();
String nextValueCode = nextValue.getCode();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
private void addMoney(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Money nextValue) {
if (!nextValue.getValueElement().isEmpty()) {
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = nextValue.getCurrency();
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
if (isBlank(searchTerm)) {
return;
}
if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), resourceName, StringNormalizer.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), nextSpDef.getName(), StringNormalizer.normalizeString(value), value);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
ArrayList<PathAndRef> retVal = new ArrayList<>();
String[] nextPathsSplit = SPLIT_R4.split(theNextSpDef.getPath());
for (String path : nextPathsSplit) {
path = path.trim();
if (isNotBlank(path)) {
for (Object next : extractValues(path, theResource)) {
retVal.add(new PathAndRef(path, next));
}
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
// TODO: implement
return Collections.emptySet();
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
ResourceIndexedSearchParamDate nextEntity;
if (nextObject instanceof BaseDateTimeType) {
BaseDateTimeType nextValue = (BaseDateTimeType) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) {
continue;
}
TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
}
}
if (nextValue.getRepeat().hasBounds()) {
if (nextValue.getRepeat().getBoundsPeriod().getStart() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getStart());
}
if (nextValue.getRepeat().getBoundsPeriod().getEnd() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getEnd());
}
}
if (dates.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
} else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
}
if (nextEntity != null) {
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof Duration) {
Duration nextValue = (Duration) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
if (SearchParamConstants.UCUM_NS.equals(nextValue.getSystem())) {
if (isNotBlank(nextValue.getCode())) {
Unit<? extends javax.measure.quantity.Quantity> unit = Unit.valueOf(nextValue.getCode());
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
Duration newValue = new Duration();
newValue.setSystem(SearchParamConstants.UCUM_NS);
newValue.setCode(NonSI.DAY.toString());
newValue.setValue(dayValue);
nextValue = newValue;
/*
* @SuppressWarnings("unchecked") PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>>)
* UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if (unit.isCompatible(UCUM.DAY)) {
*
* @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY);
* double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); Duration newValue = new Duration(); newValue.setSystem(UCUM_NS);
* newValue.setCode(UCUM.DAY.getSymbol()); newValue.setValue(dayValue); nextValue=newValue; }
*/
}
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof Quantity) {
Quantity nextValue = (Quantity) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof IntegerType) {
IntegerType nextValue = (IntegerType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof DecimalType) {
DecimalType nextValue = (DecimalType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof Quantity) {
Quantity nextValue = (Quantity) nextObject;
addQuantity(theEntity, retVal, resourceName, nextValue);
} else if (nextObject instanceof Money) {
Money nextValue = (Money) nextObject;
addMoney(theEntity, retVal, resourceName, nextValue);
} else if (nextObject instanceof Range) {
Range nextValue = (Range) nextObject;
addQuantity(theEntity, retVal, resourceName, nextValue.getLow());
addQuantity(theEntity, retVal, resourceName, nextValue.getHigh());
} else if (ourIgnoredForSearchDatatypes.contains(nextObject.getClass())) {
continue;
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<>();
String resourceName = getContext().getResourceDefinition(theResource).getName();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue;
}
String nextPath = nextSpDef.getPath();
String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) {
// // TODO: implement phonetic, and any others that have no path
//
// // TODO: do we still need this check?
// if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) {
// Questionnaire q = (Questionnaire) theResource;
// String title = "";// q.getGroup().getTitle();
// addSearchTerm(theEntity, retVal, nextSpName, title);
// }
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else {
if (nextObject instanceof HumanName) {
ArrayList<StringType> allNames = new ArrayList<>();
HumanName nextHumanName = (HumanName) nextObject;
if (isNotBlank(nextHumanName.getFamily())) {
allNames.add(nextHumanName.getFamilyElement());
}
allNames.addAll(nextHumanName.getGiven());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof Address) {
ArrayList<StringType> allNames = new ArrayList<>();
Address nextAddress = (Address) nextObject;
allNames.addAll(nextAddress.getLine());
allNames.add(nextAddress.getCityElement());
allNames.add(nextAddress.getStateElement());
allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextContact = (ContactPoint) nextObject;
if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
}
} else if (nextObject instanceof Quantity) {
BigDecimal value = ((Quantity) nextObject).getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
} else if (nextObject instanceof Range) {
Quantity low = ((Range) nextObject).getLow();
if (low != null) {
BigDecimal value = low.getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
}
} else {
throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<>();
String useSystem = null;
if (theResource instanceof CodeSystem) {
CodeSystem cs = (CodeSystem) theResource;
useSystem = cs.getUrl();
}
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue;
}
String resourceType = theEntity.getResourceType();
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
List<String> systems = new ArrayList<>();
List<String> codes = new ArrayList<>();
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
// Patient:language
if (nextObject instanceof PatientCommunicationComponent) {
PatientCommunicationComponent nextValue = (PatientCommunicationComponent) nextObject;
nextObject = nextValue.getLanguage();
}
if (nextObject instanceof Identifier) {
Identifier nextValue = (Identifier) nextObject;
if (nextValue.isEmpty()) {
continue;
}
String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
String value = nextValue.getValueElement().getValue();
if (isNotBlank(value)) {
systems.add(system);
codes.add(value);
}
if (isNotBlank(nextValue.getType().getText())) {
addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextValue = (ContactPoint) nextObject;
if (nextValue.isEmpty()) {
continue;
}
systems.add(nextValue.getSystemElement().getValueAsString());
codes.add(nextValue.getValueElement().getValue());
} else if (nextObject instanceof Enumeration<?>) {
Enumeration<?> obj = (Enumeration<?>) nextObject;
String system = extractSystem(obj);
String code = obj.getValueAsString();
if (isNotBlank(code)) {
systems.add(system);
codes.add(code);
}
} else if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
if (nextValue.isEmpty()) {
continue;
}
if ("CodeSystem.concept.code".equals(nextPath)) {
systems.add(useSystem);
} else {
systems.add(null);
}
codes.add(nextValue.getValueAsString());
} else if (nextObject instanceof Coding) {
Coding nextValue = (Coding) nextObject;
extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
} else if (nextObject instanceof CodeableConcept) {
CodeableConcept nextCC = (CodeableConcept) nextObject;
if (!nextCC.getTextElement().isEmpty()) {
addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
}
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
} else if (nextObject instanceof CapabilityStatementRestSecurityComponent) {
// Conformance.security search param points to something kind of useless right now - This should probably
// be fixed.
CapabilityStatementRestSecurityComponent sec = (CapabilityStatementRestSecurityComponent) nextObject;
for (CodeableConcept nextCC : sec.getService()) {
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
}
} else if (nextObject instanceof LocationPositionComponent) {
ourLog.warn("Position search not currently supported, not indexing location");
continue;
} else if (nextObject instanceof StructureDefinition.StructureDefinitionContextComponent) {
ourLog.warn("StructureDefinition context indexing not currently supported"); // TODO: implement this
continue;
} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " with path " + nextPath + " is of unexpected datatype: " + nextObject.getClass());
}
}
assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
Set<Pair<String, String>> haveValues = new HashSet<>();
for (int i = 0; i < systems.size(); i++) {
String system = systems.get(i);
String code = codes.get(i);
if (isBlank(system) && isBlank(code)) {
continue;
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
Pair<String, String> nextPair = Pair.of(system, code);
if (haveValues.contains(nextPair)) {
continue;
}
haveValues.add(nextPair);
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof UriType) {
UriType nextValue = (UriType) nextObject;
if (isBlank(nextValue.getValue())) {
continue;
}
ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
for (Coding nextCoding : theCodeableConcept.getCoding()) {
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
}
}
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
RuntimeSearchParam theParameterDef, Coding nextCoding) {
if (nextCoding != null && !nextCoding.isEmpty()) {
String nextSystem = nextCoding.getSystemElement().getValueAsString();
String nextCode = nextCoding.getCodeElement().getValue();
if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
theSystems.add(nextSystem);
theCodes.add(nextCode);
}
if (!nextCoding.getDisplayElement().isEmpty()) {
addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
}
}
}
/**
* Override parent because we're using FHIRPath here
*/
@Override
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
List<Object> values = new ArrayList<>();
String[] nextPathsSplit = SPLIT_R4.split(thePaths);
for (String nextPath : nextPathsSplit) {
List<Base> allValues;
try {
allValues = myFhirPathEngine.evaluate((Base) theResource, nextPath);
} catch (FHIRException e) {
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
throw new InternalErrorException(msg, e);
}
if (allValues.isEmpty() == false) {
values.addAll(allValues);
}
}
for (int i = 0; i < values.size(); i++) {
Object nextObject = values.get(i);
if (nextObject instanceof Extension) {
Extension nextExtension = (Extension) nextObject;
nextObject = nextExtension.getValue();
values.set(i, nextObject);
}
}
return values;
}
@VisibleForTesting
void setValidationSupportForTesting(org.hl7.fhir.r4.hapi.ctx.IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
private class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
private static class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
private Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@ -802,6 +159,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
ResourceType resourceType = ResourceType.fromCode(url.getResourceType());
if (resourceType != null) {
retVal = new Resource() {
private static final long serialVersionUID = -5303169871827706447L;
@Override
public Resource copy() {
return this;
@ -835,11 +194,4 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
}
}
private static <T extends Enum<?>> String extractSystem(Enumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
}

View File

@ -20,47 +20,26 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.r5.model.Location.LocationPositionComponent;
import org.hl7.fhir.r5.model.Patient.PatientCommunicationComponent;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Supplier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorR5.class);
@Autowired
private IValidationSupport myValidationSupport;
private FHIRPathEngine myFhirPathEngine;
@ -81,730 +60,103 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
myFhirPathEngine.setHostServices(new SearchParamExtractorR5HostServices());
}
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, IBase theQuantity) {
BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity");
BaseRuntimeChildDefinition quantityValueChild = quantityDefinition.getChildByName("value");
BaseRuntimeChildDefinition quantitySystemChild = quantityDefinition.getChildByName("system");
BaseRuntimeChildDefinition quantityCodeChild = quantityDefinition.getChildByName("code");
Optional<IPrimitiveType<BigDecimal>> valueField = quantityValueChild.getAccessor().getFirstValueOrNull(theQuantity);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = quantitySystemChild.getAccessor().<IPrimitiveType<String>>getFirstValueOrNull(theQuantity).map(t-> t.getValue()).orElse(null);
String code = quantityCodeChild.getAccessor().<IPrimitiveType<String>>getFirstValueOrNull(theQuantity).map(t-> t.getValue()).orElse(null);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, system, code);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
private void addMoney(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, IBase theMoney) {
BaseRuntimeElementCompositeDefinition<?> moneyDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Money");
BaseRuntimeChildDefinition moneyValueChild = moneyDefinition.getChildByName("value");
BaseRuntimeChildDefinition moneyCurrencyChild = moneyDefinition.getChildByName("currency");
Optional<IPrimitiveType<BigDecimal>> valueField = moneyValueChild.getAccessor().getFirstValueOrNull(theMoney);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = moneyCurrencyChild.getAccessor().<IPrimitiveType<String>>getFirstValueOrNull(theMoney).map(t-> t.getValue()).orElse(null);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
if (isBlank(searchTerm)) {
return;
}
if (searchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
searchTerm = searchTerm.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), resourceName, StringNormalizer.normalizeString(searchTerm), searchTerm);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
private void addStringParam(ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> retVal, RuntimeSearchParam nextSpDef, String value) {
if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
}
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), nextSpDef.getName(), StringNormalizer.normalizeString(value), value);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
ArrayList<PathAndRef> retVal = new ArrayList<>();
protected IValueExtractor getPathValueExtractor(IBaseResource theResource, String nextPath) {
return () -> myFhirPathEngine.evaluate((Base) theResource, nextPath);
}
String[] nextPathsSplit = SPLIT_R4.split(theNextSpDef.getPath());
for (String path : nextPathsSplit) {
path = path.trim();
if (isNotBlank(path)) {
for (Object next : extractValues(path, theResource)) {
retVal.add(new PathAndRef(path, next));
}
}
private static class SearchParamExtractorR5HostServices implements FHIRPathEngine.IEvaluationContext {
private Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
return null;
}
return retVal;
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
return null;
}
@Override
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
// TODO: implement
return Collections.emptySet();
}
@Override
public boolean log(String argument, List<Base> focus) {
return false;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
@Override
public FunctionDetails resolveFunction(String functionName) {
return null;
}
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue;
}
@Override
public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
return null;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
return null;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
@Override
public Base resolveReference(Object theAppContext, String theUrl) throws FHIRException {
/*
* When we're doing resolution within the SearchParamExtractor, if we want
* to do a resolve() it's just to check the type, so there is no point
* going through the heavyweight test. We can just return a stub and
* that's good enough since we're just doing something like
* Encounter.patient.where(resolve() is Patient)
*/
IdType url = new IdType(theUrl);
Base retVal = null;
if (isNotBlank(url.getResourceType())) {
retVal = myResourceTypeToStub.get(url.getResourceType());
if (retVal != null) {
return retVal;
}
ResourceIndexedSearchParamDate nextEntity;
if (nextObject instanceof BaseDateTimeType) {
BaseDateTimeType nextValue = (BaseDateTimeType) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) {
continue;
}
TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
ResourceType resourceType = ResourceType.fromCode(url.getResourceType());
if (resourceType != null) {
retVal = new Resource() {
private static final long serialVersionUID = 2368522971330181178L;
@Override
public Resource copy() {
return this;
}
}
if (nextValue.getRepeat().hasBounds()) {
if (nextValue.getRepeat().getBoundsPeriod().getStart() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getStart());
@Override
public ResourceType getResourceType() {
return resourceType;
}
if (nextValue.getRepeat().getBoundsPeriod().getEnd() != null) {
dates.add(nextValue.getRepeat().getBoundsPeriod().getEnd());
@Override
public String fhirType() {
return url.getResourceType();
}
}
if (dates.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
} else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
}
if (nextEntity != null) {
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
};
myResourceTypeToStub.put(url.getResourceType(), retVal);
}
}
return retVal;
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof Duration) {
Duration nextValue = (Duration) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
if (SearchParamConstants.UCUM_NS.equals(nextValue.getSystem())) {
if (isNotBlank(nextValue.getCode())) {
Unit<? extends javax.measure.quantity.Quantity> unit = Unit.valueOf(nextValue.getCode());
javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
double dayValue = dayConverter.convert(nextValue.getValue().doubleValue());
Duration newValue = new Duration();
newValue.setSystem(SearchParamConstants.UCUM_NS);
newValue.setCode(NonSI.DAY.toString());
newValue.setValue(dayValue);
nextValue = newValue;
/*
* @SuppressWarnings("unchecked") PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>>)
* UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if (unit.isCompatible(UCUM.DAY)) {
*
* @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit; UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY);
* double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); Duration newValue = new Duration(); newValue.setSystem(UCUM_NS);
* newValue.setCode(UCUM.DAY.getSymbol()); newValue.setValue(dayValue); nextValue=newValue; }
*/
}
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof Quantity) {
Quantity nextValue = (Quantity) nextObject;
if (nextValue.getValueElement().isEmpty()) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof IntegerType) {
IntegerType nextValue = (IntegerType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof DecimalType) {
DecimalType nextValue = (DecimalType) nextObject;
if (nextValue.getValue() == null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
@Override
public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
return false;
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue;
}
String nextPath = nextSpDef.getPath();
for (IBase nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || nextObject.isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
String typeName = getContext().getElementDefinition(nextObject.getClass()).getName();
if (typeName.equals("Quantity")) {
addQuantity(theEntity, retVal, resourceName, nextObject);
} else if (typeName.equals("Money")) {
addMoney(theEntity, retVal, resourceName, nextObject);
} else if (nextObject instanceof Range) {
Range nextValue = (Range) nextObject;
addQuantity(theEntity, retVal, resourceName, nextValue.getLow());
addQuantity(theEntity, retVal, resourceName, nextValue.getHigh());
} else if (!getIgnoredForSearchDatatypes().contains(nextObject.getClass())) {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
@Override
public ValueSet resolveValueSet(Object theO, String theS) {
return null;
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<>();
String resourceName = getContext().getResourceDefinition(theResource).getName();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue;
}
String nextPath = nextSpDef.getPath();
String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) {
// // TODO: implement phonetic, and any others that have no path
//
// // TODO: do we still need this check?
// if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) {
// Questionnaire q = (Questionnaire) theResource;
// String title = "";// q.getGroup().getTitle();
// addSearchTerm(theEntity, retVal, nextSpName, title);
// }
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else {
if (nextObject instanceof HumanName) {
ArrayList<StringType> allNames = new ArrayList<>();
HumanName nextHumanName = (HumanName) nextObject;
if (isNotBlank(nextHumanName.getFamily())) {
allNames.add(nextHumanName.getFamilyElement());
}
allNames.addAll(nextHumanName.getGiven());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof Address) {
ArrayList<StringType> allNames = new ArrayList<>();
Address nextAddress = (Address) nextObject;
allNames.addAll(nextAddress.getLine());
allNames.add(nextAddress.getCityElement());
allNames.add(nextAddress.getStateElement());
allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement());
for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextContact = (ContactPoint) nextObject;
if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
}
} else if (nextObject instanceof Quantity) {
BigDecimal value = ((Quantity) nextObject).getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
} else if (nextObject instanceof Range) {
Quantity low = ((Range) nextObject).getLow();
if (low != null) {
BigDecimal value = low.getValue();
if (value != null) {
addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
}
}
} else {
throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
}
return retVal;
}
/*
* (non-Javadoc)
*
* @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IBaseResource)
*/
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<>();
String useSystem = null;
if (theResource instanceof CodeSystem) {
CodeSystem cs = (CodeSystem) theResource;
useSystem = cs.getUrl();
}
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue;
}
String resourceType = theEntity.getResourceType();
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
List<String> systems = new ArrayList<>();
List<String> codes = new ArrayList<>();
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
// Patient:language
if (nextObject instanceof PatientCommunicationComponent) {
PatientCommunicationComponent nextValue = (PatientCommunicationComponent) nextObject;
nextObject = nextValue.getLanguage();
}
if (nextObject instanceof Identifier) {
Identifier nextValue = (Identifier) nextObject;
if (nextValue.isEmpty()) {
continue;
}
String system = StringUtils.defaultIfBlank(nextValue.getSystemElement().getValueAsString(), null);
String value = nextValue.getValueElement().getValue();
if (isNotBlank(value)) {
systems.add(system);
codes.add(value);
}
if (isNotBlank(nextValue.getType().getText())) {
addStringParam(theEntity, retVal, nextSpDef, nextValue.getType().getText());
}
} else if (nextObject instanceof ContactPoint) {
ContactPoint nextValue = (ContactPoint) nextObject;
if (nextValue.isEmpty()) {
continue;
}
systems.add(nextValue.getSystemElement().getValueAsString());
codes.add(nextValue.getValueElement().getValue());
} else if (nextObject instanceof IBaseEnumeration<?>) {
IBaseEnumeration<?> obj = (IBaseEnumeration<?>) nextObject;
String system = extractSystem(obj);
String code = obj.getValueAsString();
if (isNotBlank(code)) {
systems.add(system);
codes.add(code);
}
} else if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
if (nextValue.isEmpty()) {
continue;
}
if ("CodeSystem.concept.code".equals(nextPath)) {
systems.add(useSystem);
} else {
systems.add(null);
}
codes.add(nextValue.getValueAsString());
} else if (nextObject instanceof Coding) {
Coding nextValue = (Coding) nextObject;
extractTokensFromCoding(systems, codes, theEntity, retVal, nextSpDef, nextValue);
} else if (nextObject instanceof CodeableConcept) {
CodeableConcept nextCC = (CodeableConcept) nextObject;
if (!nextCC.getTextElement().isEmpty()) {
addStringParam(theEntity, retVal, nextSpDef, nextCC.getTextElement().getValue());
}
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
} else if (nextObject instanceof CapabilityStatementRestSecurityComponent) {
// Conformance.security search param points to something kind of useless right now - This should probably
// be fixed.
CapabilityStatementRestSecurityComponent sec = (CapabilityStatementRestSecurityComponent) nextObject;
for (CodeableConcept nextCC : sec.getService()) {
extractTokensFromCodeableConcept(systems, codes, nextCC, theEntity, retVal, nextSpDef);
}
} else if (nextObject instanceof LocationPositionComponent) {
ourLog.warn("Position search not currently supported, not indexing location");
continue;
} else if (nextObject instanceof StructureDefinition.StructureDefinitionContextComponent) {
ourLog.warn("StructureDefinition context indexing not currently supported"); // TODO: implement this
continue;
} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " with path " + nextPath + " is of unexpected datatype: " + nextObject.getClass());
}
}
assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
Set<Pair<String, String>> haveValues = new HashSet<>();
for (int i = 0; i < systems.size(); i++) {
String system = systems.get(i);
String code = codes.get(i);
if (isBlank(system) && isBlank(code)) {
continue;
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
code = code.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
}
Pair<String, String> nextPair = Pair.of(system, code);
if (haveValues.contains(nextPair)) {
continue;
}
haveValues.add(nextPair);
ResourceIndexedSearchParamToken nextEntity;
nextEntity = new ResourceIndexedSearchParamToken(nextSpDef.getName(), system, code);
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
}
return retVal;
}
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IBase) nextObject).isEmpty()) {
continue;
}
String resourceName = nextSpDef.getName();
if (nextObject instanceof UriType) {
UriType nextValue = (UriType) nextObject;
if (isBlank(nextValue.getValue())) {
continue;
}
ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
}
}
}
return retVal;
}
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
for (Coding nextCoding : theCodeableConcept.getCoding()) {
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
}
}
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
RuntimeSearchParam theParameterDef, Coding nextCoding) {
if (nextCoding != null && !nextCoding.isEmpty()) {
String nextSystem = nextCoding.getSystemElement().getValueAsString();
String nextCode = nextCoding.getCodeElement().getValue();
if (isNotBlank(nextSystem) || isNotBlank(nextCode)) {
theSystems.add(nextSystem);
theCodes.add(nextCode);
}
if (!nextCoding.getDisplayElement().isEmpty()) {
addStringParam(theEntity, theListToPopulate, theParameterDef, nextCoding.getDisplayElement().getValue());
}
}
}
@Override
protected Supplier<List<? extends IBase>> getPathValueExtractor(IBaseResource theResource, String nextPath) {
return () -> {
try {
IWorkerContext worker = new HapiWorkerContext(getContext(), myValidationSupport);
FHIRPathEngine fp = new FHIRPathEngine(worker);
fp.setHostServices(new SearchParamExtractorR5HostServices());
return fp.evaluate((Base) theResource, nextPath);
} catch (FHIRException e) {
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
throw new InternalErrorException(msg, e);
}
};
}
private static class SearchParamExtractorR5HostServices implements FHIRPathEngine.IEvaluationContext {
private Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
return null;
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
return null;
}
@Override
public boolean log(String argument, List<Base> focus) {
return false;
}
@Override
public FunctionDetails resolveFunction(String functionName) {
return null;
}
@Override
public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
return null;
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
return null;
}
@Override
public Base resolveReference(Object theAppContext, String theUrl) throws FHIRException {
/*
* When we're doing resolution within the SearchParamExtractor, if we want
* to do a resolve() it's just to check the type, so there is no point
* going through the heavyweight test. We can just return a stub and
* that's good enough since we're just doing something like
* Encounter.patient.where(resolve() is Patient)
*/
IdType url = new IdType(theUrl);
Base retVal = null;
if (isNotBlank(url.getResourceType())) {
retVal = myResourceTypeToStub.get(url.getResourceType());
if (retVal != null) {
return retVal;
}
ResourceType resourceType = ResourceType.fromCode(url.getResourceType());
if (resourceType != null) {
retVal = new Resource() {
@Override
public Resource copy() {
return this;
}
@Override
public ResourceType getResourceType() {
return resourceType;
}
@Override
public String fhirType() {
return url.getResourceType();
}
};
myResourceTypeToStub.put(url.getResourceType(), retVal);
}
}
return retVal;
}
@Override
public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
return false;
}
@Override
public ValueSet resolveValueSet(Object theO, String theS) {
return null;
}
}
private static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
}

View File

@ -256,7 +256,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active");
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active");
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);
@ -378,7 +378,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active");
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active");
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);
@ -480,7 +480,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.extension('http://acme.org/extension')");
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.extension('http://acme.org/extension')");
assertEquals(2, values.size());
assertTrue(values.get(0) instanceof IBaseExtension);
assertTrue(values.get(0) instanceof Extension);
@ -691,7 +691,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active");
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active");
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);
@ -792,7 +792,7 @@ public class FhirTerserR4Test {
public void testGetValuesWithTheCreate() {
Patient p = new Patient();
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active", true);
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active", true);
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);
@ -841,7 +841,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active");
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active");
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);
@ -957,7 +957,7 @@ public class FhirTerserR4Test {
System.out.println(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p));
List<Object> values = ourCtx.newTerser().getValues(p, "Patient.active", true);
List<IBase> values = ourCtx.newTerser().getValues(p, "Patient.active", true);
assertEquals(1, values.size());
assertTrue(values.get(0) instanceof PrimitiveType);
assertTrue(values.get(0) instanceof BooleanType);

View File

@ -1,24 +1,24 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Test;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.*;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
public class FhirTerserTest {
private static FhirContext ourCtx = FhirContext.forR4();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirTerserTest.class);
@Test
public void testGetAllPopulatedChildElementsOfType() {
@ -53,13 +53,13 @@ public class FhirTerserTest {
// As string
{
List<Object> values = t.getValues(obs, "Observation.valueString");
List<IBase> values = t.getValues(obs, "Observation.valueString");
assertEquals(0, values.size());
}
// As quantity
{
List<Object> values = t.getValues(obs, "Observation.valueQuantity");
List<IBase> values = t.getValues(obs, "Observation.valueQuantity");
assertEquals(1, values.size());
Quantity actual = (Quantity) values.get(0);
assertEquals("123", actual.getValueElement().getValueAsString());