
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}