
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}