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