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}