From 99cd4ce5f72d97ec3f20ed034ec812ffafe6ba40 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 17 Oct 2018 09:47:35 -0400 Subject: [PATCH] Allow for _total parameter --- .../java/ca/uhn/fhir/rest/api/Constants.java | 1 + .../fhir/rest/api/SearchTotalModeEnum.java | 34 ++++++++++++ .../ca/uhn/fhir/rest/api/SummaryEnum.java | 2 +- .../fhir/rest/gclient/IClientExecutable.java | 2 +- .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 10 ++++ .../uhn/fhir/rest/client/impl/BaseClient.java | 5 +- .../fhir/rest/client/impl/GenericClient.java | 42 ++++++++------- .../uhn/fhir/jpa/dao/SearchParameterMap.java | 50 +++++++++-------- .../jpa/search/SearchCoordinatorSvcImpl.java | 5 +- .../FhirResourceDaoR4SearchOptimizedTest.java | 21 ++++---- .../r4/ResourceProviderSummaryModeR4Test.java | 5 +- .../fhir/rest/server/method/MethodUtil.java | 2 + .../method/SearchTotalModeParameter.java | 53 +++++++++++++++++++ .../resources/vm/jpa_resource_provider.vm | 6 ++- 14 files changed, 174 insertions(+), 64 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SearchTotalModeEnum.java create mode 100644 hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchTotalModeParameter.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java index fb89858339e..4fc0ab5e5a4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java @@ -197,6 +197,7 @@ public class Constants { * This is provided for testing only! Use with caution as this property may change. */ public static final String TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS = "TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS"; + public static final String PARAM_SEARCH_TOTAL_MODE = "_total"; static { CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SearchTotalModeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SearchTotalModeEnum.java new file mode 100644 index 00000000000..d10613d01a3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SearchTotalModeEnum.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.rest.api; + +import java.util.HashMap; +import java.util.Map; + +public enum SearchTotalModeEnum { + + NONE("none"), + ESTIMATED("estimated"), + ACCURATE("accurate"); + + private static volatile Map ourCodeToEnum; + private final String myCode; + + SearchTotalModeEnum(String theCode) { + myCode = theCode; + } + + public String getCode() { + return myCode; + } + + public static SearchTotalModeEnum fromCode(String theCode) { + Map map = ourCodeToEnum; + if (map == null) { + map = new HashMap<>(); + for (SearchTotalModeEnum next : values()) { + map.put(next.getCode(), next); + } + ourCodeToEnum = map; + } + return map.get(theCode); + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java index 035ac2d6105..5467902c8b8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java @@ -67,7 +67,7 @@ public enum SummaryEnum { public static SummaryEnum fromCode(String theCode) { Map c2s = ourCodeToSummary; if (c2s == null) { - c2s = new HashMap(); + c2s = new HashMap<>(); for (SummaryEnum next : values()) { c2s.put(next.getCode(), next); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java index 9a7dd47a8d2..331575d2605 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java @@ -89,6 +89,6 @@ public interface IClientExecutable, Y> { /** * Request that the server modify the response using the _summary param */ - T summaryMode(SummaryEnum... theSummary); + T summaryMode(SummaryEnum theSummary); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index 9086999b87b..e820e39c1be 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -2,7 +2,9 @@ package ca.uhn.fhir.rest.gclient; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.api.SearchStyleEnum; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -80,6 +82,14 @@ public interface IQuery extends IBaseQuery>, IClientExecutable IQuery returnBundle(Class theClass); + /** + * Request that the server modify the response using the _total param + * + * THIS IS AN EXPERIMENTAL FEATURE - Use with caution, as it may be + * removed or modified in a future version. + */ + IQuery totalMode(SearchTotalModeEnum theTotalMode); + /** * Add a "_revinclude" specification * diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index f00a1818605..0b447575917 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -205,7 +205,7 @@ public abstract class BaseClient implements IRestfulClient { } T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, - boolean theLogRequestAndResponse, List theSummaryMode, Set theSubsetElements, CacheControlDirective theCacheControlDirective) { + boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set theSubsetElements, CacheControlDirective theCacheControlDirective) { if (!myDontValidateConformance) { myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this); @@ -227,8 +227,7 @@ public abstract class BaseClient implements IRestfulClient { } if (theSummaryMode != null) { - List summaryModeStrings = theSummaryMode.stream().map(SummaryEnum::getCode).collect(Collectors.toList());; - params.put(Constants.PARAM_SUMMARY, summaryModeStrings); + params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode())); } else if (mySummary != null) { params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode())); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index f7c8b5eb14d..991e87f6907 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -98,7 +98,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } private T doReadOrVRead(final Class theType, IIdType theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, - List theSummary, EncodingEnum theEncoding, Set theSubsetElements) { + SummaryEnum theSummary, EncodingEnum theEncoding, Set theSubsetElements) { String resName = toResourceName(theType); IIdType id = theId; if (!id.hasBaseUrl()) { @@ -127,8 +127,8 @@ public class GenericClient extends BaseClient implements IGenericClient { invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); } - boolean allowHtmlResponse = (theSummary != null && theSummary.contains(SummaryEnum.TEXT)) || (theSummary == null && getSummary() == SummaryEnum.TEXT); - ResourceResponseHandler binding = new ResourceResponseHandler(theType, (Class) null, id, allowHtmlResponse); + boolean allowHtmlResponse = SummaryEnum.TEXT.equals(theSummary); + ResourceResponseHandler binding = new ResourceResponseHandler<>(theType, (Class) null, id, allowHtmlResponse); if (theNotModifiedHandler == null) { return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null); @@ -368,7 +368,7 @@ public class GenericClient extends BaseClient implements IGenericClient { protected EncodingEnum myParamEncoding; protected Boolean myPrettyPrint; - protected List mySummaryMode; + protected SummaryEnum mySummaryMode; protected CacheControlDirective myCacheControlDirective; private List> myPreferResponseTypes; private boolean myQueryLogRequestAndResponse; @@ -483,15 +483,8 @@ public class GenericClient extends BaseClient implements IGenericClient { @SuppressWarnings("unchecked") @Override - public T summaryMode(SummaryEnum... theSummary) { - mySummaryMode = null; - if (theSummary != null) { - if (theSummary.length == 1) { - mySummaryMode = Collections.singletonList(theSummary[0]); - } else { - mySummaryMode = Arrays.asList(theSummary); - } - } + public T summaryMode(SummaryEnum theSummary) { + mySummaryMode = theSummary; return ((T) this); } @@ -1657,20 +1650,21 @@ public class GenericClient extends BaseClient implements IGenericClient { private class SearchInternal extends BaseSearch, IQuery, OUTPUT> implements IQuery, IUntypedQuery> { private String myCompartmentName; - private List myInclude = new ArrayList(); + private List myInclude = new ArrayList<>(); private DateRangeParam myLastUpdated; private Integer myParamLimit; - private List> myProfiles = new ArrayList>(); + private List> myProfiles = new ArrayList<>(); private String myResourceId; private String myResourceName; private Class myResourceType; private Class myReturnBundleType; - private List myRevInclude = new ArrayList(); + private List myRevInclude = new ArrayList<>(); private SearchStyleEnum mySearchStyle; private String mySearchUrl; - private List mySecurity = new ArrayList(); - private List mySort = new ArrayList(); - private List myTags = new ArrayList(); + private List mySecurity = new ArrayList<>(); + private List mySort = new ArrayList<>(); + private List myTags = new ArrayList<>(); + private SearchTotalModeEnum myTotalMode; public SearchInternal() { myResourceType = null; @@ -1792,6 +1786,10 @@ public class GenericClient extends BaseClient implements IGenericClient { } } + if (myTotalMode != null) { + addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode()); + } + IClientResponseHandler binding; binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType)); @@ -1843,6 +1841,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return count(theLimitTo); } + @Override + public IQuery totalMode(SearchTotalModeEnum theSearchTotalModeEnum) { + myTotalMode = theSearchTotalModeEnum; + return this; + } + @Override public IQuery returnBundle(Class theClass) { if (theClass == null) { 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 a525889b905..f9c38feeed7 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 @@ -5,10 +5,7 @@ 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.SortOrderEnum; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.util.ObjectUtil; @@ -31,9 +28,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * 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. @@ -54,7 +51,8 @@ public class SearchParameterMap extends LinkedHashMap myRevIncludes; private SortSpec mySort; - private Set mySummaryMode; + private SummaryEnum mySummaryMode; + private SearchTotalModeEnum mySearchTotalMode; /** * Constructor @@ -70,20 +68,22 @@ public class SearchParameterMap extends LinkedHashMap getSummaryMode() { - if (mySummaryMode == null) { - return Collections.emptySet(); - } - return Collections.unmodifiableSet(mySummaryMode); + public SummaryEnum getSummaryMode() { + return mySummaryMode; } - public void setSummaryMode(Set theSummaryMode) { + 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; @@ -261,7 +261,7 @@ public class SearchParameterMap extends LinkedHashMap> nextParamName : values()) { for (List nextAnd : nextParamName) { for (IQueryParameterType nextOr : nextAnd) { @@ -408,12 +408,10 @@ public class SearchParameterMap extends LinkedHashMap> { private final FhirContext myCtx; - public QueryParameterOrComparator(FhirContext theCtx) { + QueryParameterOrComparator(FhirContext theCtx) { myCtx = theCtx; } @@ -505,7 +503,7 @@ public class SearchParameterMap extends LinkedHashMap theMethodBinding) throws InternalErrorException, InvalidRequestException { + return getTypeForRequestOrThrowInvalidRequestException(theRequest); + } + + @Override + public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { + // ignore + } + + public static SearchTotalModeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) { + String[] searchTotalModeVal = theRequest.getParameters().get(Constants.PARAM_SEARCH_TOTAL_MODE); + if (searchTotalModeVal != null && searchTotalModeVal.length > 0) { + return SearchTotalModeEnum.fromCode(searchTotalModeVal[0]); + } + + return null; + } + +} diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index 87015ff5717..ac572a98850 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -19,6 +19,7 @@ import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; public class ${className}ResourceProvider extends ## We have specialized base classes for RPs that handle certain resource types. These @@ -133,7 +134,9 @@ public class ${className}ResourceProvider extends @ca.uhn.fhir.rest.annotation.Count Integer theCount, - Set theSummaryMode + SummaryEnum theSummaryMode, + + SearchTotalModeEnum theSearchTotalMode ) { startRequest(theServletRequest); @@ -156,6 +159,7 @@ public class ${className}ResourceProvider extends paramMap.setSort(theSort); paramMap.setCount(theCount); paramMap.setSummaryMode(theSummaryMode); + paramMap.setSearchTotalMode(theSearchTotalMode); getDao().translateRawParameters(theAdditionalRawParams, paramMap);