ca.uhn.fhir.jpa.searchparam.SearchParameterMap Maven / Gradle / Ivy
/*
* #%L
* HAPI FHIR JPA - Search Parameters
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.searchparam;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.util.UrlUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.NOT_EQUAL;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SearchParameterMap implements Serializable {
public static final Integer INTEGER_0 = 0;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class);
private static final long serialVersionUID = 1L;
private final HashMap>> mySearchParameterMap = new LinkedHashMap<>();
private Integer myCount;
private Integer myOffset;
private EverythingModeEnum myEverythingMode = null;
private Set myIncludes;
private DateRangeParam myLastUpdated;
private boolean myLoadSynchronous;
private Integer myLoadSynchronousUpTo;
private Set myRevIncludes;
private SortSpec mySort;
private SummaryEnum mySummaryMode;
private SearchTotalModeEnum mySearchTotalMode;
private QuantityParam myNearDistanceParam;
private boolean myLastN;
private Integer myLastNMax;
private boolean myDeleteExpunge;
private SearchContainedModeEnum mySearchContainedMode = SearchContainedModeEnum.FALSE;
/**
* Constructor
*/
public SearchParameterMap() {
super();
}
/**
* Constructor
*/
public SearchParameterMap(String theName, IQueryParameterType theParam) {
add(theName, theParam);
}
/**
* Creates and returns a copy of this map
*/
@JsonIgnore
@Override
public SearchParameterMap clone() {
SearchParameterMap map = new SearchParameterMap();
map.setSummaryMode(getSummaryMode());
map.setSort(getSort());
map.setSearchTotalMode(getSearchTotalMode());
map.setRevIncludes(getRevIncludes());
map.setIncludes(getIncludes());
map.setEverythingMode(getEverythingMode());
map.setCount(getCount());
map.setDeleteExpunge(isDeleteExpunge());
map.setLastN(isLastN());
map.setLastNMax(getLastNMax());
map.setLastUpdated(getLastUpdated());
map.setLoadSynchronous(isLoadSynchronous());
map.setNearDistanceParam(getNearDistanceParam());
map.setLoadSynchronousUpTo(getLoadSynchronousUpTo());
map.setOffset(getOffset());
map.setSearchContainedMode(getSearchContainedMode());
for (Map.Entry>> entry : mySearchParameterMap.entrySet()) {
List> andParams = entry.getValue();
List> newAndParams = new ArrayList<>();
for (List orParams : andParams) {
List newOrParams = new ArrayList<>(orParams);
newAndParams.add(newOrParams);
}
map.put(entry.getKey(), newAndParams);
}
return map;
}
public SummaryEnum getSummaryMode() {
return mySummaryMode;
}
public void setSummaryMode(SummaryEnum theSummaryMode) {
mySummaryMode = theSummaryMode;
}
public SearchTotalModeEnum getSearchTotalMode() {
return mySearchTotalMode;
}
public void setSearchTotalMode(SearchTotalModeEnum theSearchTotalMode) {
mySearchTotalMode = theSearchTotalMode;
}
public SearchParameterMap add(String theName, DateParam theDateParam) {
add(theName, (IQueryParameterOr>) theDateParam);
return this;
}
@SuppressWarnings("unchecked")
public SearchParameterMap add(String theName, IQueryParameterAnd> theAnd) {
if (theAnd == null) {
return this;
}
if (!containsKey(theName)) {
put(theName, new ArrayList<>());
}
List> paramList = get(theName);
for (IQueryParameterOr> next : theAnd.getValuesAsQueryTokens()) {
if (next == null) {
continue;
}
paramList.add((List) next.getValuesAsQueryTokens());
}
return this;
}
public SearchParameterMap add(String theName, IQueryParameterOr> theOr) {
if (theOr == null) {
return this;
}
if (!containsKey(theName)) {
put(theName, new ArrayList<>());
}
get(theName).add((List) theOr.getValuesAsQueryTokens());
return this;
}
public Collection>> values() {
return mySearchParameterMap.values();
}
public SearchParameterMap add(String theName, IQueryParameterType theParam) {
assert !Constants.PARAM_LASTUPDATED.equals(theName); // this has it's own field in the map
if (theParam == null) {
return this;
}
if (!containsKey(theName)) {
put(theName, new ArrayList<>());
}
ArrayList list = new ArrayList<>();
list.add(theParam);
get(theName).add(list);
return this;
}
public SearchParameterMap addInclude(Include theInclude) {
getIncludes().add(theInclude);
return this;
}
private void addLastUpdateParam(StringBuilder theBuilder, ParamPrefixEnum thePrefix, DateParam theDateParam) {
if (theDateParam != null && isNotBlank(theDateParam.getValueAsString())) {
addUrlParamSeparator(theBuilder);
theBuilder.append(Constants.PARAM_LASTUPDATED);
theBuilder.append('=');
theBuilder.append(thePrefix.getValue());
theBuilder.append(theDateParam.getValueAsString());
}
}
public SearchParameterMap addRevInclude(Include theInclude) {
getRevIncludes().add(theInclude);
return this;
}
private void addUrlIncludeParams(StringBuilder b, String paramName, Set theList) {
ArrayList list = new ArrayList<>(theList);
list.sort(new IncludeComparator());
for (Include nextInclude : list) {
addUrlParamSeparator(b);
b.append(paramName);
if (nextInclude.isRecurse()) {
b.append(Constants.PARAM_INCLUDE_QUALIFIER_RECURSE);
}
b.append('=');
if (Constants.INCLUDE_STAR.equals(nextInclude.getValue())) {
b.append(Constants.INCLUDE_STAR);
} else {
b.append(UrlUtil.escapeUrlParam(nextInclude.getParamType()));
b.append(':');
b.append(UrlUtil.escapeUrlParam(nextInclude.getParamName()));
if (isNotBlank(nextInclude.getParamTargetType())) {
b.append(':');
b.append(nextInclude.getParamTargetType());
}
}
}
}
private void addUrlParamSeparator(StringBuilder theB) {
if (theB.length() == 0) {
theB.append('?');
} else {
theB.append('&');
}
}
public Integer getCount() {
return myCount;
}
public SearchParameterMap setCount(Integer theCount) {
myCount = theCount;
return this;
}
public Integer getOffset() {
return myOffset;
}
public void setOffset(Integer theOffset) {
myOffset = theOffset;
}
public EverythingModeEnum getEverythingMode() {
return myEverythingMode;
}
public void setEverythingMode(EverythingModeEnum theConsolidateMatches) {
myEverythingMode = theConsolidateMatches;
}
public Set getIncludes() {
if (myIncludes == null) {
myIncludes = new HashSet<>();
}
return myIncludes;
}
public void setIncludes(Set theIncludes) {
myIncludes = theIncludes;
}
/**
* Returns null if there is no last updated value
*/
public DateRangeParam getLastUpdated() {
if (myLastUpdated != null) {
if (myLastUpdated.isEmpty()) {
myLastUpdated = null;
}
}
return myLastUpdated;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
myLastUpdated = theLastUpdated;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results
*/
public Integer getLoadSynchronousUpTo() {
return myLoadSynchronousUpTo;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results. Note that setting this to a value will also set
* {@link #setLoadSynchronous(boolean)} to true
*/
public SearchParameterMap setLoadSynchronousUpTo(Integer theLoadSynchronousUpTo) {
myLoadSynchronousUpTo = theLoadSynchronousUpTo;
if (myLoadSynchronousUpTo != null) {
setLoadSynchronous(true);
}
return this;
}
public Set getRevIncludes() {
if (myRevIncludes == null) {
myRevIncludes = new HashSet<>();
}
return myRevIncludes;
}
public void setRevIncludes(Set theRevIncludes) {
myRevIncludes = theRevIncludes;
}
public SortSpec getSort() {
return mySort;
}
public SearchParameterMap setSort(SortSpec theSort) {
mySort = theSort;
return this;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results
*/
public boolean isLoadSynchronous() {
return myLoadSynchronous;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results
*/
public SearchParameterMap setLoadSynchronous(boolean theLoadSynchronous) {
myLoadSynchronous = theLoadSynchronous;
return this;
}
/**
* If set, tells the server to use an Elasticsearch query to generate a list of
* Resource IDs for the LastN operation
*/
public boolean isLastN() {
return myLastN;
}
/**
* If set, tells the server to use an Elasticsearch query to generate a list of
* Resource IDs for the LastN operation
*/
public SearchParameterMap setLastN(boolean theLastN) {
myLastN = theLastN;
return this;
}
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public Integer getLastNMax() {
return myLastNMax;
}
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public SearchParameterMap setLastNMax(Integer theLastNMax) {
myLastNMax = theLastNMax;
return this;
}
/**
* This method creates a URL query string representation of the parameters in this
* object, excluding the part before the parameters, e.g.
*
* ?name=smith&_sort=Patient:family
*
*
* This method excludes the _count
parameter,
* as it doesn't affect the substance of the results returned
*
*/
public String toNormalizedQueryString(FhirContext theCtx) {
StringBuilder b = new StringBuilder();
ArrayList keys = new ArrayList<>(keySet());
Collections.sort(keys);
for (String nextKey : keys) {
List> nextValuesAndsIn = get(nextKey);
List> nextValuesAndsOut = new ArrayList<>();
for (List extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
List nextValuesOrsOut = new ArrayList<>();
nextValuesOrsOut.addAll(nextValuesAndIn);
nextValuesOrsOut.sort(new QueryParameterTypeComparator(theCtx));
if (nextValuesOrsOut.size() > 0) {
nextValuesAndsOut.add(nextValuesOrsOut);
}
} // for AND
nextValuesAndsOut.sort(new QueryParameterOrComparator(theCtx));
for (List nextValuesAnd : nextValuesAndsOut) {
addUrlParamSeparator(b);
IQueryParameterType firstValue = nextValuesAnd.get(0);
b.append(UrlUtil.escapeUrlParam(nextKey));
if (firstValue.getMissing() != null) {
b.append(Constants.PARAMQUALIFIER_MISSING);
b.append('=');
if (firstValue.getMissing()) {
b.append(Constants.PARAMQUALIFIER_MISSING_TRUE);
} else {
b.append(Constants.PARAMQUALIFIER_MISSING_FALSE);
}
continue;
}
if (isNotBlank(firstValue.getQueryParameterQualifier())) {
b.append(firstValue.getQueryParameterQualifier());
}
b.append('=');
for (int i = 0; i < nextValuesAnd.size(); i++) {
IQueryParameterType nextValueOr = nextValuesAnd.get(i);
if (i > 0) {
b.append(',');
}
String valueAsQueryToken = nextValueOr.getValueAsQueryToken(theCtx);
valueAsQueryToken = defaultString(valueAsQueryToken);
b.append(UrlUtil.escapeUrlParam(valueAsQueryToken));
}
}
} // for keys
SortSpec sort = getSort();
boolean first = true;
while (sort != null) {
if (isNotBlank(sort.getParamName())) {
if (first) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_SORT);
b.append('=');
first = false;
} else {
b.append(',');
}
if (sort.getOrder() == SortOrderEnum.DESC) {
b.append('-');
}
b.append(sort.getParamName());
}
Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen
sort = sort.getChain();
}
if (hasIncludes()) {
addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes());
}
addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes());
if (getLastUpdated() != null) {
DateParam lb = getLastUpdated().getLowerBound();
DateParam ub = getLastUpdated().getUpperBound();
if (isNotEqualsComparator(lb, ub)) {
addLastUpdateParam(b, NOT_EQUAL, getLastUpdated().getLowerBound());
} else {
addLastUpdateParam(b, GREATERTHAN_OR_EQUALS, lb);
addLastUpdateParam(b, LESSTHAN_OR_EQUALS, ub);
}
}
if (getCount() != null) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(getCount());
}
if (getOffset() != null) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_OFFSET);
b.append('=');
b.append(getOffset());
}
// Summary mode (_summary)
if (getSummaryMode() != null) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_SUMMARY);
b.append('=');
b.append(getSummaryMode().getCode());
}
// Search count mode (_total)
if (getSearchTotalMode() != null) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_SEARCH_TOTAL_MODE);
b.append('=');
b.append(getSearchTotalMode().getCode());
}
// Contained mode
// For some reason, instead of null here, we default to false. That said, ommitting it is identical to setting
// it to false.
if (getSearchContainedMode() != SearchContainedModeEnum.FALSE) {
addUrlParamSeparator(b);
b.append(Constants.PARAM_CONTAINED);
b.append("=");
b.append(getSearchContainedMode().getCode());
}
if (b.length() == 0) {
b.append('?');
}
return b.toString();
}
private boolean isNotEqualsComparator(DateParam theLowerBound, DateParam theUpperBound) {
return theLowerBound != null
&& theUpperBound != null
&& theLowerBound.getPrefix().equals(NOT_EQUAL)
&& theUpperBound.getPrefix().equals(NOT_EQUAL);
}
/**
* @since 5.5.0
*/
public boolean hasIncludes() {
return myIncludes != null && !myIncludes.isEmpty();
}
/**
* @since 6.2.0
*/
public boolean hasRevIncludes() {
return myRevIncludes != null && !myRevIncludes.isEmpty();
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
if (!isEmpty()) {
b.append("params", mySearchParameterMap);
}
if (!getIncludes().isEmpty()) {
b.append("includes", getIncludes());
}
return b.toString();
}
public void clean() {
for (Map.Entry>> nextParamEntry : this.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List> andOrParams = nextParamEntry.getValue();
cleanParameter(nextParamName, andOrParams);
}
}
/*
* Given a particular named parameter, e.g. `name`, iterate over AndOrParams and remove any which are empty.
*/
private void cleanParameter(String theParamName, List> theAndOrParams) {
theAndOrParams.forEach(orList -> {
List emptyParameters = orList.stream()
.filter(nextOr -> nextOr.getMissing() == null)
.filter(nextOr -> nextOr instanceof QuantityParam)
.filter(nextOr -> isBlank(((QuantityParam) nextOr).getValueAsString()))
.collect(Collectors.toList());
ourLog.debug("Ignoring empty parameter: {}", theParamName);
orList.removeAll(emptyParameters);
});
theAndOrParams.removeIf(List::isEmpty);
}
public QuantityParam getNearDistanceParam() {
return myNearDistanceParam;
}
public void setNearDistanceParam(QuantityParam theQuantityParam) {
myNearDistanceParam = theQuantityParam;
}
public boolean isWantOnlyCount() {
return SummaryEnum.COUNT.equals(getSummaryMode()) || INTEGER_0.equals(getCount());
}
public boolean isDeleteExpunge() {
return myDeleteExpunge;
}
public SearchParameterMap setDeleteExpunge(boolean theDeleteExpunge) {
myDeleteExpunge = theDeleteExpunge;
return this;
}
public List> get(String theName) {
return mySearchParameterMap.get(theName);
}
public void put(String theName, List> theParams) {
mySearchParameterMap.put(theName, theParams);
}
public boolean containsKey(String theName) {
return mySearchParameterMap.containsKey(theName);
}
public Set keySet() {
return mySearchParameterMap.keySet();
}
public boolean isEmpty() {
return mySearchParameterMap.isEmpty();
}
// Wrapper methods
public Set>>> entrySet() {
return mySearchParameterMap.entrySet();
}
public List> remove(String theName) {
return mySearchParameterMap.remove(theName);
}
/**
* Variant of removeByNameAndModifier for unmodified params.
*
* @param theName the query parameter key
* @return an And/Or List of Query Parameters matching the name with no modifier.
*/
public List> removeByNameUnmodified(String theName) {
return this.removeByNameAndModifier(theName, "");
}
/**
* Given a search parameter name and modifier (e.g. :text),
* get and remove all Search Parameters matching this name and modifier
*
* @param theName the query parameter key
* @param theModifier the qualifier you want to remove - nullable for unmodified params.
* @return an And/Or List of Query Parameters matching the qualifier.
*/
public List> removeByNameAndModifier(String theName, String theModifier) {
theModifier = StringUtils.defaultString(theModifier, "");
List> remainderParameters = new ArrayList<>();
List> matchingParameters = new ArrayList<>();
// pull all of them out, partition by match against the qualifier
List> andList = mySearchParameterMap.remove(theName);
if (andList != null) {
for (List orList : andList) {
if (!orList.isEmpty()
&& StringUtils.defaultString(orList.get(0).getQueryParameterQualifier(), "")
.equals(theModifier)) {
matchingParameters.add(orList);
} else {
remainderParameters.add(orList);
}
}
}
// put the unmatched back in.
if (!remainderParameters.isEmpty()) {
mySearchParameterMap.put(theName, remainderParameters);
}
return matchingParameters;
}
public List> removeByNameAndModifier(
String theName, @Nonnull TokenParamModifier theModifier) {
return removeByNameAndModifier(theName, theModifier.getValue());
}
/**
* For each search parameter in the map, extract any which have the given qualifier.
* e.g. Take the url: Observation?code:text=abc&code=123&code:text=def&reason:text=somereason
*
* If we call this function with `:text`, it will return a map that looks like:
*
* code -> [[code:text=abc], [code:text=def]]
* reason -> [[reason:text=somereason]]
*
* and the remaining search parameters in the map will be:
*
* code -> [[code=123]]
*
* @param theQualifier
* @return
*/
public Map>> removeByQualifier(String theQualifier) {
Map>> retVal = new HashMap<>();
Set parameterNames = mySearchParameterMap.keySet();
for (String parameterName : parameterNames) {
List> paramsWithQualifier = removeByNameAndModifier(parameterName, theQualifier);
retVal.put(parameterName, paramsWithQualifier);
}
return retVal;
}
public Map>> removeByQualifier(@Nonnull TokenParamModifier theModifier) {
return removeByQualifier(theModifier.getValue());
}
public int size() {
return mySearchParameterMap.size();
}
public SearchContainedModeEnum getSearchContainedMode() {
return mySearchContainedMode;
}
public void setSearchContainedMode(SearchContainedModeEnum theSearchContainedMode) {
if (theSearchContainedMode == null) {
mySearchContainedMode = SearchContainedModeEnum.FALSE;
} else {
this.mySearchContainedMode = theSearchContainedMode;
}
}
/**
* Returns true if {@link #getOffset()} and {@link #getCount()} both return a non null response
*
* @since 5.5.0
*/
public boolean isOffsetQuery() {
return getOffset() != null && getCount() != null;
}
public enum EverythingModeEnum {
/*
* Don't reorder! We rely on the ordinals
*/
ENCOUNTER_INSTANCE(false, true, true),
ENCOUNTER_TYPE(false, true, false),
PATIENT_INSTANCE(true, false, true),
PATIENT_TYPE(true, false, false);
private final boolean myEncounter;
private final boolean myInstance;
private final boolean myPatient;
EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
assert thePatient ^ theEncounter;
myPatient = thePatient;
myEncounter = theEncounter;
myInstance = theInstance;
}
public boolean isEncounter() {
return myEncounter;
}
public boolean isInstance() {
return myInstance;
}
public boolean isPatient() {
return myPatient;
}
}
static int compare(FhirContext theCtx, IQueryParameterType theO1, IQueryParameterType theO2) {
CompareToBuilder b = new CompareToBuilder();
b.append(theO1.getMissing(), theO2.getMissing());
b.append(theO1.getQueryParameterQualifier(), theO2.getQueryParameterQualifier());
if (b.toComparison() == 0) {
b.append(theO1.getValueAsQueryToken(theCtx), theO2.getValueAsQueryToken(theCtx));
}
return b.toComparison();
}
public static SearchParameterMap newSynchronous() {
SearchParameterMap retVal = new SearchParameterMap();
retVal.setLoadSynchronous(true);
return retVal;
}
public static SearchParameterMap newSynchronous(String theName, IQueryParameterType theParam) {
SearchParameterMap retVal = new SearchParameterMap();
retVal.setLoadSynchronous(true);
retVal.add(theName, theParam);
return retVal;
}
public static class IncludeComparator implements Comparator {
@Override
public int compare(Include theO1, Include theO2) {
int retVal = StringUtils.compare(theO1.getParamType(), theO2.getParamType());
if (retVal == 0) {
retVal = StringUtils.compare(theO1.getParamName(), theO2.getParamName());
}
if (retVal == 0) {
retVal = StringUtils.compare(theO1.getParamTargetType(), theO2.getParamTargetType());
}
return retVal;
}
}
public static class QueryParameterOrComparator implements Comparator> {
private final FhirContext myCtx;
QueryParameterOrComparator(FhirContext theCtx) {
myCtx = theCtx;
}
@Override
public int compare(List theO1, List theO2) {
// These lists will never be empty
return SearchParameterMap.compare(myCtx, theO1.get(0), theO2.get(0));
}
}
public static class QueryParameterTypeComparator implements Comparator {
private final FhirContext myCtx;
QueryParameterTypeComparator(FhirContext theCtx) {
myCtx = theCtx;
}
@Override
public int compare(IQueryParameterType theO1, IQueryParameterType theO2) {
return SearchParameterMap.compare(myCtx, theO1, theO2);
}
}
public List getAllChainsInOrder() {
final List allChainsInOrder = new ArrayList<>();
for (SortSpec sortSpec = getSort(); sortSpec != null; sortSpec = sortSpec.getChain()) {
allChainsInOrder.add(sortSpec);
}
return Collections.unmodifiableList(allChainsInOrder);
}
}