001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 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 size = size(); 177 if (size == 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 if (size > 0) { 183 retval.addAll(getResources(0, size)); 184 } 185 return retval; 186 } 187 188 /** 189 * Returns the UUID associated with this search. Note that this 190 * does not need to return a non-null value unless it a 191 * IPagingProvider is being used that requires UUIDs 192 * being returned. 193 * <p> 194 * In other words, if you are using the default FifoMemoryPagingProvider in 195 * your server, it is fine for this method to simply return {@code null} since FifoMemoryPagingProvider 196 * does not use the value anyhow. On the other hand, if you are creating a custom 197 * IPagingProvider implementation you might use this method to communicate 198 * the search ID back to the provider. 199 * </p> 200 * <p> 201 * Note that the UUID returned by this method corresponds to 202 * the search, and not to the individual page. 203 * </p> 204 */ 205 @Nullable 206 String getUuid(); 207 208 /** 209 * Optionally may be used to signal a preferred page size to the server, e.g. because 210 * the implementing code recognizes that the resources which will be returned by this 211 * implementation are expensive to load so a smaller page size should be used. The value 212 * returned by this method will only be used if the client has not explicitly requested 213 * a page size. 214 * 215 * @return Returns the preferred page size or <code>null</code> 216 */ 217 Integer preferredPageSize(); 218 219 /** 220 * Returns the total number of results which match the given query (exclusive of any 221 * _include's or OperationOutcome). May return {@literal null} if the total size is not 222 * known or would be too expensive to calculate. 223 */ 224 @Nullable 225 Integer size(); 226 227 /** 228 * This method returns <code>false</code> if the bundle provider knows that at least 229 * one result exists. 230 */ 231 default boolean isEmpty() { 232 Integer size = size(); 233 if (size != null) { 234 return size == 0; 235 } 236 return getResources(0, 1).isEmpty(); 237 } 238 239 /** 240 * @return the value of {@link #size()} and throws a {@link NullPointerException} of it is null 241 */ 242 default int sizeOrThrowNpe() { 243 Integer retVal = size(); 244 Validate.notNull(retVal, "size() returned null"); 245 return retVal; 246 } 247 248 /** 249 * @return the list of ids of all resources in the bundle 250 */ 251 default List<String> getAllResourceIds() { 252 return getAllResources().stream() 253 .map(resource -> resource.getIdElement().getIdPart()) 254 .collect(Collectors.toList()); 255 } 256}