
001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.api.server; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.rest.server.method.ResponsePage; 025import jakarta.annotation.Nonnull; 026import jakarta.annotation.Nullable; 027import org.apache.commons.lang3.Validate; 028import org.hl7.fhir.instance.model.api.IBaseResource; 029import org.hl7.fhir.instance.model.api.IPrimitiveType; 030 031import java.util.ArrayList; 032import java.util.Date; 033import java.util.List; 034import java.util.stream.Collectors; 035 036public interface IBundleProvider { 037 038 /** 039 * If this method is implemented, provides an ID for the current 040 * page of results. This ID should be unique (at least within 041 * the current search as identified by {@link #getUuid()}) 042 * so that it can be used to look up a specific page of results. 043 * <p> 044 * This can be used in order to allow the 045 * server paging mechanism to work using completely 046 * opaque links (links that do not encode any index/offset 047 * information), which can be useful on some servers. 048 * </p> 049 * 050 * @since 3.5.0 051 */ 052 default String getCurrentPageId() { 053 return null; 054 } 055 056 /** 057 * If this method is implemented, provides an ID for the next 058 * page of results. This ID should be unique (at least within 059 * the current search as identified by {@link #getUuid()}) 060 * so that it can be used to look up a specific page of results. 061 * <p> 062 * This can be used in order to allow the 063 * server paging mechanism to work using completely 064 * opaque links (links that do not encode any index/offset 065 * information), which can be useful on some servers. 066 * </p> 067 * 068 * @since 3.5.0 069 */ 070 default String getNextPageId() { 071 return null; 072 } 073 074 /** 075 * If this method is implemented, provides an ID for the previous 076 * page of results. This ID should be unique (at least within 077 * the current search as identified by {@link #getUuid()}) 078 * so that it can be used to look up a specific page of results. 079 * <p> 080 * This can be used in order to allow the 081 * server paging mechanism to work using completely 082 * opaque links (links that do not encode any index/offset 083 * information), which can be useful on some servers. 084 * </p> 085 * 086 * @since 3.5.0 087 */ 088 default String getPreviousPageId() { 089 return null; 090 } 091 092 /** 093 * If the results in this bundle were produced using an offset query (as opposed to a query using 094 * continuation pointers, page IDs, etc.) the page offset can be returned here. The server 095 * should then attempt to form paging links that use <code>_offset</code> instead of 096 * opaque page IDs. 097 */ 098 default Integer getCurrentPageOffset() { 099 return null; 100 } 101 102 /** 103 * If {@link #getCurrentPageOffset()} returns a non-null value, this method must also return 104 * the actual page size used 105 */ 106 default Integer getCurrentPageSize() { 107 return null; 108 } 109 110 /** 111 * Returns the instant as of which this result was created. The 112 * result of this value is used to populate the <code>lastUpdated</code> 113 * value on search result/history result bundles. 114 */ 115 IPrimitiveType<Date> getPublished(); 116 117 /** 118 * Load the given collection of resources by index, plus any additional resources per the 119 * server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example, 120 * if the method is invoked with index 0,10 the method might return 10 search results, plus an 121 * additional 20 resources which matched a client's _include specification. 122 * </p> 123 * <p> 124 * Note that if this bundle provider was loaded using a 125 * page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(RequestDetails, String, String)} 126 * because {@link #getNextPageId()} provided a value on the 127 * previous page, then the indexes should be ignored and the 128 * whole page returned. 129 * </p> 130 * Note that this implementation should not be used if accurate paging is required, 131 * as page calculation depends on _include'd resource counts. 132 * For accurate paging, use {@link IBundleProvider#getResources(int, int, ResponsePage.ResponsePageBuilder)} 133 * 134 * @param theFromIndex The low index (inclusive) to return 135 * @param theToIndex The high index (exclusive) to return 136 * @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>. 137 */ 138 @Nonnull 139 default List<IBaseResource> getResources(int theFromIndex, int theToIndex) { 140 return getResources(theFromIndex, theToIndex, new ResponsePage.ResponsePageBuilder()); 141 } 142 143 /** 144 * Load the given collection of resources by index, plus any additional resources per the 145 * server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example, 146 * if the method is invoked with index 0,10 the method might return 10 search results, plus an 147 * additional 20 resources which matched a client's _include specification. 148 * <p> 149 * Note that if this bundle provider was loaded using a 150 * page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(RequestDetails, String, String)} 151 * because {@link #getNextPageId()} provided a value on the 152 * previous page, then the indexes should be ignored and the 153 * whole page returned. 154 * </p> 155 * 156 * @param theFromIndex The low index (inclusive) to return 157 * @param theToIndex The high index (exclusive) to return 158 * @param theResponsePageBuilder The ResponsePageBuilder. The builder will add values needed for the response page. 159 * @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>. 160 */ 161 default List<IBaseResource> getResources( 162 int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) { 163 return getResources(theFromIndex, theToIndex); 164 } 165 166 /** 167 * Get all resources 168 * 169 * @return <code>getResources(0, this.size())</code>. Return an empty list if <code>this.size()</code> is zero. 170 * @throws ConfigurationException if size() is null 171 */ 172 @Nonnull 173 default List<IBaseResource> getAllResources() { 174 List<IBaseResource> retval = new ArrayList<>(); 175 176 Integer sizeI = size(); 177 if (sizeI == null) { 178 throw new ConfigurationException( 179 Msg.code(464) 180 + "Attempt to request all resources from an asynchronous search result. The SearchParameterMap for this search probably should have been synchronous."); 181 } 182 int size = containsAllResources() ? getResourceListComplete().size() : sizeI.intValue(); 183 if (size > 0) { 184 retval.addAll(getResources(0, size)); 185 } 186 return retval; 187 } 188 189 /** 190 * Returns all resources contained in this provider (outcomes and includes included). 191 * This may return more than size() resources. 192 * But if no implementation is provided, it will return what getAllResources() returns 193 * (which is limited by size()) 194 * @return 195 */ 196 default List<IBaseResource> getResourceListComplete() { 197 return getAllResources(); 198 } 199 200 /** 201 * Returns the UUID associated with this search. Note that this 202 * does not need to return a non-null value unless it a 203 * IPagingProvider is being used that requires UUIDs 204 * being returned. 205 * <p> 206 * In other words, if you are using the default FifoMemoryPagingProvider in 207 * your server, it is fine for this method to simply return {@code null} since FifoMemoryPagingProvider 208 * does not use the value anyhow. On the other hand, if you are creating a custom 209 * IPagingProvider implementation you might use this method to communicate 210 * the search ID back to the provider. 211 * </p> 212 * <p> 213 * Note that the UUID returned by this method corresponds to 214 * the search, and not to the individual page. 215 * </p> 216 */ 217 @Nullable 218 String getUuid(); 219 220 /** 221 * Optionally may be used to signal a preferred page size to the server, e.g. because 222 * the implementing code recognizes that the resources which will be returned by this 223 * implementation are expensive to load so a smaller page size should be used. The value 224 * returned by this method will only be used if the client has not explicitly requested 225 * a page size. 226 * 227 * @return Returns the preferred page size or <code>null</code> 228 */ 229 Integer preferredPageSize(); 230 231 /** 232 * Returns the total number of results which match the given query (exclusive of any 233 * _include's or OperationOutcome). May return {@literal null} if the total size is not 234 * known or would be too expensive to calculate. 235 */ 236 @Nullable 237 Integer size(); 238 239 /** 240 * Whether or not this bundle provider contains all resources specified in the total. 241 * This can be the case if a provider has all the resources and passes them back directly 242 * (as is the case for some plain/hybrid providers that return lists of resources. 243 * @return 244 */ 245 default boolean containsAllResources() { 246 return false; 247 } 248 249 /** 250 * This method returns <code>false</code> if the bundle provider knows that at least 251 * one result exists. 252 */ 253 default boolean isEmpty() { 254 Integer size = size(); 255 if (size != null) { 256 return size == 0; 257 } 258 return getResources(0, 1).isEmpty(); 259 } 260 261 /** 262 * @return the value of {@link #size()} and throws a {@link NullPointerException} of it is null 263 */ 264 default int sizeOrThrowNpe() { 265 Integer retVal = size(); 266 Validate.notNull(retVal, "size() returned null"); 267 return retVal; 268 } 269 270 /** 271 * @return the list of ids of all resources in the bundle 272 */ 273 default List<String> getAllResourceIds() { 274 return getAllResources().stream() 275 .map(resource -> resource.getIdElement().getIdPart()) 276 .collect(Collectors.toList()); 277 } 278}