001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2023 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.server.method;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.api.HookParams;
025import ca.uhn.fhir.interceptor.api.Pointcut;
026import ca.uhn.fhir.model.api.Include;
027import ca.uhn.fhir.model.valueset.BundleTypeEnum;
028import ca.uhn.fhir.rest.api.Constants;
029import ca.uhn.fhir.rest.api.EncodingEnum;
030import ca.uhn.fhir.rest.api.RequestTypeEnum;
031import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
032import ca.uhn.fhir.rest.api.server.IBundleProvider;
033import ca.uhn.fhir.rest.api.server.IRestfulServer;
034import ca.uhn.fhir.rest.api.server.RequestDetails;
035import ca.uhn.fhir.rest.server.IPagingProvider;
036import ca.uhn.fhir.rest.server.RestfulServerUtils;
037import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
038import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
039import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
040import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
041import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
042import ca.uhn.fhir.util.ReflectionUtil;
043import org.hl7.fhir.instance.model.api.IBaseResource;
044
045import javax.annotation.Nonnull;
046import java.lang.reflect.Method;
047import java.util.HashSet;
048import java.util.Set;
049
050import static org.apache.commons.lang3.StringUtils.isBlank;
051import static org.apache.commons.lang3.StringUtils.isNotBlank;
052
053public class PageMethodBinding extends BaseResourceReturningMethodBinding {
054
055        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PageMethodBinding.class);
056
057        public PageMethodBinding(FhirContext theContext, Method theMethod) {
058                super(null, theMethod, theContext, null);
059        }
060
061        public IBaseResource provider() {
062                return null;
063        }
064
065        @Override
066        protected BundleTypeEnum getResponseBundleType() {
067                return null;
068        }
069
070        @Override
071        public ReturnTypeEnum getReturnType() {
072                return ReturnTypeEnum.BUNDLE;
073        }
074
075        @Override
076        public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
077                return handlePagingRequest(theServer, theRequest, theRequest.getParameters().get(Constants.PARAM_PAGINGACTION)[0]);
078        }
079
080        @Override
081        public IBaseResource doInvokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) {
082                return handlePagingRequest(theServer, theRequest, theRequest.getParameters().get(Constants.PARAM_PAGINGACTION)[0]);
083        }
084
085        private IBaseResource handlePagingRequest(IRestfulServer<?> theServer, RequestDetails theRequest, String thePagingAction) {
086                IPagingProvider pagingProvider = theServer.getPagingProvider();
087                if (pagingProvider == null) {
088                        throw new InvalidRequestException(Msg.code(416) + "This server does not support paging");
089                }
090
091                // Interceptor invoke: SERVER_INCOMING_REQUEST_PRE_HANDLED
092                populateRequestDetailsForInterceptor(theRequest, ReflectionUtil.EMPTY_OBJECT_ARRAY);
093                HookParams preHandledParams = new HookParams();
094                preHandledParams.add(RestOperationTypeEnum.class, theRequest.getRestOperationType());
095                preHandledParams.add(RequestDetails.class, theRequest);
096                preHandledParams.addIfMatchesType(ServletRequestDetails.class, theRequest);
097                if (theRequest.getInterceptorBroadcaster() != null) {
098                        theRequest
099                                .getInterceptorBroadcaster()
100                                .callHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, preHandledParams);
101                }
102
103                Integer offsetI;
104                int start = 0;
105                IBundleProvider bundleProvider;
106
107                String pageId = null;
108                String[] pageIdParams = theRequest.getParameters().get(Constants.PARAM_PAGEID);
109                if (pageIdParams != null) {
110                        if (pageIdParams.length > 0) {
111                                if (isNotBlank(pageIdParams[0])) {
112                                        pageId = pageIdParams[0];
113                                }
114                        }
115                }
116
117                if (pageId != null) {
118                        // This is a page request by Search ID and Page ID
119
120                        bundleProvider = pagingProvider.retrieveResultList(theRequest, thePagingAction, pageId);
121                        validateHaveBundleProvider(thePagingAction, bundleProvider);
122
123                } else {
124                        // This is a page request by Search ID and Offset
125
126                        bundleProvider = pagingProvider.retrieveResultList(theRequest, thePagingAction);
127                        validateHaveBundleProvider(thePagingAction, bundleProvider);
128
129                        offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET);
130                        if (offsetI == null || offsetI < 0) {
131                                offsetI = 0;
132                        }
133
134                        Integer totalNum = bundleProvider.size();
135                        start = offsetI;
136                        if (totalNum != null) {
137                                start = Math.min(start, totalNum);
138                        }
139                }
140
141                ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, theServer.getDefaultResponseEncoding());
142
143                Set<Include> includes = new HashSet<>();
144                String[] reqIncludes = theRequest.getParameters().get(Constants.PARAM_INCLUDE);
145                if (reqIncludes != null) {
146                        for (String nextInclude : reqIncludes) {
147                                includes.add(new Include(nextInclude));
148                        }
149                }
150
151                String linkSelfBase = theRequest.getFhirServerBase();
152                String completeUrl = theRequest.getCompleteUrl();
153                String linkSelf = linkSelfBase + completeUrl.substring(theRequest.getCompleteUrl().indexOf('?'));
154
155                BundleTypeEnum bundleType = null;
156                String[] bundleTypeValues = theRequest.getParameters().get(Constants.PARAM_BUNDLETYPE);
157                if (bundleTypeValues != null) {
158                        bundleType = BundleTypeEnum.VALUESET_BINDER.fromCodeString(bundleTypeValues[0]);
159                }
160
161                EncodingEnum encodingEnum = null;
162                if (responseEncoding != null) {
163                        encodingEnum = responseEncoding.getEncoding();
164                }
165
166                Integer count = RestfulServerUtils.extractCountParameter(theRequest);
167                if (count == null) {
168                        count = pagingProvider.getDefaultPageSize();
169                } else if (count > pagingProvider.getMaximumPageSize()) {
170                        count = pagingProvider.getMaximumPageSize();
171                }
172
173                return createBundleFromBundleProvider(theServer, theRequest, count, linkSelf, includes, bundleProvider, start, bundleType, encodingEnum, thePagingAction);
174        }
175
176        private void validateHaveBundleProvider(String thePagingAction, IBundleProvider theBundleProvider) {
177                // Return an HTTP 410 if the search is not known
178                if (theBundleProvider == null) {
179                        ourLog.info("Client requested unknown paging ID[{}]", thePagingAction);
180                        String msg = getContext().getLocalizer().getMessage(PageMethodBinding.class, "unknownSearchId", thePagingAction);
181                        throw new ResourceGoneException(Msg.code(417) + msg);
182                }
183        }
184
185        @Nonnull
186        @Override
187        public RestOperationTypeEnum getRestOperationType() {
188                return RestOperationTypeEnum.GET_PAGE;
189        }
190
191        @Override
192        public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
193                String[] pageId = theRequest.getParameters().get(Constants.PARAM_PAGINGACTION);
194                if (pageId == null || pageId.length == 0 || isBlank(pageId[0])) {
195                        return MethodMatchEnum.NONE;
196                }
197                if (theRequest.getRequestType() != RequestTypeEnum.GET) {
198                        return MethodMatchEnum.NONE;
199                }
200
201                return MethodMatchEnum.EXACT;
202        }
203
204
205}