diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java index bd9e777f140..8ba3b37f4d6 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/JDBCConstants.java @@ -134,7 +134,7 @@ public class JDBCConstants { supportedModifiersMap.put(Type.STRING, Arrays.asList(Modifier.EXACT, Modifier.CONTAINS, Modifier.MISSING)); supportedModifiersMap.put(Type.REFERENCE, Arrays.asList(Modifier.TYPE, Modifier.MISSING, Modifier.IDENTIFIER)); supportedModifiersMap.put(Type.URI, Arrays.asList(Modifier.BELOW, Modifier.ABOVE, Modifier.MISSING)); - supportedModifiersMap.put(Type.TOKEN, Arrays.asList(Modifier.MISSING, Modifier.NOT)); + supportedModifiersMap.put(Type.TOKEN, Arrays.asList(Modifier.MISSING, Modifier.NOT, Modifier.OF_TYPE)); supportedModifiersMap.put(Type.NUMBER, Arrays.asList(Modifier.MISSING)); supportedModifiersMap.put(Type.DATE, Arrays.asList(Modifier.MISSING)); supportedModifiersMap.put(Type.QUANTITY, Arrays.asList(Modifier.MISSING)); diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java index 7461567f3d8..b4cbd7c6392 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java @@ -131,6 +131,8 @@ private SearchConstants() { public static final String PARAMETER_DELIMITER = "|"; + public static final String COMPOSITE_DELIMITER = "$"; + public static final char COLON_DELIMITER = ':'; public static final String COLON_DELIMITER_STR = ":"; diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/QueryParameterValue.java b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/QueryParameterValue.java index 7acead33ccf..dcd8bc2b81a 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/parameters/QueryParameterValue.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/parameters/QueryParameterValue.java @@ -21,6 +21,7 @@ public class QueryParameterValue { private boolean hidden = false; + private boolean ofTypeModifier = false; private Prefix prefix = null; @@ -150,6 +151,22 @@ public void setHidden(boolean hidden) { this.hidden = hidden; } + /** + * Sets whether the value of an :of-type modifier. + * @return true or false + */ + public boolean isOfTypeModifier() { + return ofTypeModifier; + } + + /** + * Gets whether the value of an :of-type modifier. + * @param ofTypeModifier true if value of an :of-type modifier + */ + public void setOfTypeModifier(boolean ofTypeModifier) { + this.ofTypeModifier = ofTypeModifier; + } + /** * Serialize the ParameterValue to a query parameter string */ @@ -178,6 +195,7 @@ public String toString() { delim = outputBuilder(returnString, delim, valueString); delim = outputBuilder(returnString, delim, valueDate); + // token search with :of-type modifier is handled as a composite search if (component != null && !component.isEmpty()) { String componentDelim = ""; for (QueryParameter componentParam : component) { @@ -190,7 +208,7 @@ public String toString() { throw new IllegalStateException("Components of a composite search parameter may only have a single value"); } returnString.append(componentDelim).append(componentValues.get(0)); - componentDelim = "$"; + componentDelim = ofTypeModifier ? SearchConstants.PARAMETER_DELIMITER : SearchConstants.COMPOSITE_DELIMITER; } } return returnString.toString(); diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java index 6a592e75c27..238e4452b29 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java @@ -882,9 +882,15 @@ public static FHIRSearchContext parseQueryParameters(Class resourceType, // Build list of processed query parameters List curParameterList = new ArrayList<>(); for (String paramValueString : params) { - QueryParameter parameter = new QueryParameter(type, parameterCode, modifier, modifierResourceTypeName); List queryParameterValues = - processQueryParameterValueString(resourceType, searchParameter, modifier, parameter.getModifierResourceTypeName(), paramValueString); + processQueryParameterValueString(resourceType, searchParameter, modifier, modifierResourceTypeName, paramValueString); + QueryParameter parameter; + // Internally treat search with :of-type modifier as composite search + if (Modifier.OF_TYPE.equals(modifier)) { + parameter = new QueryParameter(Type.COMPOSITE, parameterCode + SearchConstants.OF_TYPE_MODIFIER_SUFFIX, null, null); + } else { + parameter = new QueryParameter(type, parameterCode, modifier, modifierResourceTypeName); + } parameter.getValues().addAll(queryParameterValues); curParameterList.add(parameter); parameters.add(parameter); @@ -1238,12 +1244,10 @@ private static List parseQueryParameterValuesString(SearchP if (parts.length == 2) { parameterValue.setValueSystem(unescapeSearchParm(parts[0])); parameterValue.setValueCode(unescapeSearchParm(parts[1])); - } - else { + } else { parameterValue.setValueCode(unescapeSearchParm(v)); } - } - else { + } else { String valueString = unescapeSearchParm(v); valueString = extractReferenceValue(valueString); parameterValue.setValueString(valueString); @@ -1281,6 +1285,7 @@ private static List parseQueryParameterValuesString(SearchP case TOKEN: { // token // [parameter]=[system]|[code] + // [parameter]:of-type=[system|code|value] /* * TODO: start enforcing this: * "For token parameters on elements of type ContactPoint, uri, or boolean, @@ -1288,6 +1293,33 @@ private static List parseQueryParameterValuesString(SearchP * [parameter]=[code] form is allowed */ String[] parts = v.split(SearchConstants.BACKSLASH_NEGATIVE_LOOKBEHIND + "\\|"); + if (Modifier.OF_TYPE.equals(modifier)) { + // Convert :of-type into a composite search parameter + final String ofTypeParmName = searchParameter.getCode().getValue() + SearchConstants.OF_TYPE_MODIFIER_SUFFIX; + parameterValue.setOfTypeModifier(true); + if (parts.length > 1 && parts.length < 4) { + QueryParameterValue typeParameterValue = new QueryParameterValue(); + if (parts.length == 3) { + typeParameterValue.setValueSystem(unescapeSearchParm(parts[0])); + } + typeParameterValue.setValueCode(unescapeSearchParm(parts[parts.length - 2])); + QueryParameter typeParameter = new QueryParameter(Type.TOKEN, SearchUtil.makeCompositeSubCode(ofTypeParmName, SearchConstants.OF_TYPE_MODIFIER_COMPONENT_TYPE), + null, null, Collections.singletonList(typeParameterValue)); + parameterValue.addComponent(typeParameter); + + QueryParameterValue valueParameterValue = new QueryParameterValue(); + valueParameterValue.setValueCode(unescapeSearchParm(parts[parts.length - 1])); + QueryParameter valueParameter = new QueryParameter(Type.TOKEN, SearchUtil.makeCompositeSubCode(ofTypeParmName, SearchConstants.OF_TYPE_MODIFIER_COMPONENT_VALUE), + null, null, Collections.singletonList(valueParameterValue)); + parameterValue.addComponent(valueParameter); + } else { + QueryParameterValue valueParameterValue = new QueryParameterValue(); + valueParameterValue.setValueCode(unescapeSearchParm(v)); + QueryParameter valueParameter = new QueryParameter(Type.TOKEN, SearchUtil.makeCompositeSubCode(ofTypeParmName, SearchConstants.OF_TYPE_MODIFIER_COMPONENT_VALUE), + null, null, Collections.singletonList(valueParameterValue)); + parameterValue.addComponent(valueParameter); + } + } else if (parts.length == 2) { parameterValue.setValueSystem(unescapeSearchParm(parts[0])); parameterValue.setValueCode(unescapeSearchParm(parts[1]));