diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java index 6737e592d1c..ff286b97ab2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java @@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.*; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -32,81 +33,28 @@ 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.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ParameterUtil; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.ObjectUtil; +import ca.uhn.fhir.util.UrlUtil; public class SearchParameterMap extends LinkedHashMap>> { - public class QueryParameterTypeComparator implements Comparator { - - private final FhirContext myCtx; - - public QueryParameterTypeComparator(FhirContext theCtx) { - myCtx = theCtx; - } - - @Override - public int compare(IQueryParameterType theO1, IQueryParameterType theO2) { - FhirContext ctx = myCtx; - int retVal; - - if (theO1.getMissing() == null && theO2.getMissing() == null) { - retVal = 0; - } else if (theO1.getMissing() == null) { - retVal = -1; - } else if (theO2.getMissing() == null) { - retVal = 1; - } else if (ObjectUtil.equals(theO1.getMissing(), theO2.getMissing())) { - retVal = 0; - } else { - if (theO1.getMissing().booleanValue()) { - retVal = 1; - } else { - retVal = -1; - } - } - - if (retVal == 0) { - String q1 = theO1.getQueryParameterQualifier(); - String q2 = theO2.getQueryParameterQualifier(); - retVal = StringUtils.compare(q1, q2); - } - - if (retVal == 0) { - String v1 = theO1.getValueAsQueryToken(ctx); - } - - return retVal; - } - - } - - public class QueryParameterOrComparator implements Comparator> { - - @Override - public int compare(List theO1, List theO2) { - // TODO Auto-generated method stub - return 0; - } - - - } - private static final long serialVersionUID = 1L; private Integer myCount; private EverythingModeEnum myEverythingMode = null; private Set myIncludes; private DateRangeParam myLastUpdated; + private boolean myLoadSynchronous; private Integer myLoadSynchronousUpTo; private Set myRevIncludes; private SortSpec mySort; - private boolean myLoadSynchronous; - /** * Constructor */ @@ -121,6 +69,11 @@ public class SearchParameterMap extends LinkedHashMap) theDateParam); + return this; + } + public void add(String theName, IQueryParameterAnd theAnd) { if (theAnd == null) { return; @@ -148,11 +101,6 @@ public class SearchParameterMap extends LinkedHashMap)theDateParam); - return this; - } - public SearchParameterMap add(String theName, IQueryParameterType theParam) { assert !Constants.PARAM_LASTUPDATED.equals(theName); // this has it's own field in the map @@ -165,7 +113,7 @@ public class SearchParameterMap extends LinkedHashMap list = new ArrayList(); list.add(theParam); get(theName).add(list); - + return this; } @@ -214,6 +162,14 @@ public class SearchParameterMap extends LinkedHashMap getRevIncludes() { if (myRevIncludes == null) { myRevIncludes = new HashSet(); @@ -225,6 +181,14 @@ public class SearchParameterMap extends LinkedHashMap keys = new ArrayList(keySet()); + Collections.sort(keys); + for (String nextKey : keys) { + + List> nextValuesAndsIn = get(nextKey); + List> nextValuesAndsOut = new ArrayList>(); + + for (List nextValuesAndIn : nextValuesAndsIn) { + + List nextValuesOrsOut = new ArrayList(); + for (IQueryParameterType nextValueOrIn : nextValuesAndIn) { + if (nextValueOrIn.getMissing() != null || isNotBlank(nextValueOrIn.getValueAsQueryToken(theCtx))) { + nextValuesOrsOut.add(nextValueOrIn); + } + } + + Collections.sort(nextValuesOrsOut, new QueryParameterTypeComparator(theCtx)); + + if (nextValuesOrsOut.size() > 0) { + nextValuesAndsOut.add(nextValuesOrsOut); + } + + } // for AND + + Collections.sort(nextValuesAndsOut, new QueryParameterOrComparator(theCtx)); + + for (List nextValuesAnd : nextValuesAndsOut) { + addUrlParamSeparator(b); + IQueryParameterType firstValue = nextValuesAnd.get(0); + b.append(UrlUtil.escape(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(','); + } + b.append(ParameterUtil.escapeAndUrlEncode(nextValueOr.getValueAsQueryToken(theCtx))); + } + } + + } // for keys + + SortSpec sort = getSort(); + boolean first = true; + while (sort != null) { + + if (isNotBlank(sort.getParamName())) { + if (first) { + 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(); + } + + addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes()); + addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes()); + + if (getLastUpdated() != null) { + DateParam lb = getLastUpdated().getLowerBound(); + addLastUpdateParam(b, lb); + DateParam ub = getLastUpdated().getUpperBound(); + addLastUpdateParam(b, ub); + } + + if (getCount() != null) { + b.append(Constants.PARAM_COUNT); + b.append('='); + b.append(getCount()); + } + + + return b.toString(); + } + + private void addLastUpdateParam(StringBuilder b, DateParam date) { + if (isNotBlank(date.getValueAsString())) { + b.append(Constants.PARAM_LASTUPDATED); + b.append('='); + b.append(date.getValueAsString()); + } + } + + private void addUrlIncludeParams(StringBuilder b, String paramName, Set list) { + for (Include nextInclude : list) { + b.append(paramName); + b.append('='); + b.append(UrlUtil.escape(nextInclude.getParamType())); + b.append(':'); + b.append(UrlUtil.escape(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('&'); + } + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); @@ -307,6 +389,38 @@ public class SearchParameterMap extends LinkedHashMap keys = new ArrayList(keySet()); - Collections.sort(keys); - for (String nextKey : keys) { - - List> nextValuesAndsIn = get(nextKey); - List> nextValuesAndsOut = new ArrayList>(); - - for (List nextValuesAndIn : nextValuesAndsIn) { + public class QueryParameterOrComparator implements Comparator> { + private final FhirContext myCtx; - List nextValuesOrsOut = new ArrayList(); - for (List nextValuesOrsIn : nextValuesAndsIn) { - - nextValuesOrsIn = new ArrayList(nextValuesOrsIn); - Collections.sort(nextValuesOrsIn, new QueryParameterTypeComparator()); - for (IQueryParameterType nextValueOrIn : nextValuesOrsIn) { - if (nextValueOrIn.getMissing() != null || isNotBlank(nextValueOrIn.getValueAsQueryToken(theCtx))) { - nextValuesOrsOut.add(nextValueOrIn); - } - } - - } // for OR - - if (nextValuesOrsOut.size() > 0) { - nextValuesAndsOut.add(nextValuesOrsOut); - } - - } // for AND - - Collections.sort(nextValuesAndsOut, new QueryParameterTypeComparator()); - + public 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 class QueryParameterTypeComparator implements Comparator { + + private final FhirContext myCtx; + + public QueryParameterTypeComparator(FhirContext theCtx) { + myCtx = theCtx; + } + + @Override + public int compare(IQueryParameterType theO1, IQueryParameterType theO2) { + return SearchParameterMap.compare(myCtx, theO1, theO2); + } + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SearchParameterMapTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SearchParameterMapTest.java index e7217997ad7..b606202d09d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SearchParameterMapTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SearchParameterMapTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import org.junit.AfterClass; import org.junit.Test; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.rest.param.*; @@ -18,7 +19,9 @@ public class SearchParameterMapTest { TestUtil.clearAllStaticFieldsForUnitTest(); } - + private static FhirContext ourCtx = FhirContext.forDstu3(); + + @Test public void testToQueryString() { SearchParameterMap map = new SearchParameterMap(); @@ -34,10 +37,12 @@ public class SearchParameterMapTest { .addAnd(new DateOrListParam().add(new DateParam(ParamPrefixEnum.LESSTHAN, "2002"))); map.add("birthdate", birthdateAnd); - String queryString = map.toQueryString(); + String queryString = map.toNormalizedQueryString(ourCtx); + ourLog.info(queryString); } - + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMapTest.class); + /** * {@link Search} uses these ordinals so they shouldn't get out of order */