001/* 002 * #%L 003 * HAPI FHIR - Client 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.client.method; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.model.valueset.BundleTypeEnum; 026import ca.uhn.fhir.rest.annotation.Search; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 029import ca.uhn.fhir.rest.api.SearchStyleEnum; 030import ca.uhn.fhir.rest.client.api.UrlSourceEnum; 031import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation; 032import ca.uhn.fhir.rest.param.ParameterUtil; 033import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 035import ca.uhn.fhir.util.ParametersUtil; 036import org.apache.commons.lang3.StringUtils; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038import org.hl7.fhir.instance.model.api.IIdType; 039 040import java.lang.reflect.Method; 041import java.util.Collections; 042import java.util.LinkedHashMap; 043import java.util.List; 044import java.util.Map; 045import java.util.Map.Entry; 046 047import static org.apache.commons.lang3.StringUtils.isBlank; 048 049public class SearchMethodBinding extends BaseResourceReturningMethodBinding { 050 private String myCompartmentName; 051 private String myDescription; 052 private Integer myIdParamIndex; 053 private String myQueryName; 054 055 public SearchMethodBinding( 056 Class<? extends IBaseResource> theReturnResourceType, 057 Method theMethod, 058 FhirContext theContext, 059 Object theProvider) { 060 super(theReturnResourceType, theMethod, theContext, theProvider); 061 Search search = theMethod.getAnnotation(Search.class); 062 this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null); 063 this.myCompartmentName = StringUtils.defaultIfBlank(search.compartmentName(), null); 064 this.myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext()); 065 this.myDescription = ParametersUtil.extractDescription(theMethod); 066 067 /* 068 * Check for parameter combinations and names that are invalid 069 */ 070 List<IParameter> parameters = getParameters(); 071 // List<SearchParameter> searchParameters = new ArrayList<SearchParameter>(); 072 for (int i = 0; i < parameters.size(); i++) { 073 IParameter next = parameters.get(i); 074 if (!(next instanceof SearchParameter)) { 075 continue; 076 } 077 078 SearchParameter sp = (SearchParameter) next; 079 if (sp.getName().startsWith("_")) { 080 if (ALLOWED_PARAMS.contains(sp.getName())) { 081 String msg = getContext() 082 .getLocalizer() 083 .getMessage( 084 getClass().getName() + ".invalidSpecialParamName", 085 theMethod.getName(), 086 theMethod.getDeclaringClass().getSimpleName(), 087 sp.getName()); 088 throw new ConfigurationException(Msg.code(1442) + msg); 089 } 090 } 091 092 // searchParameters.add(sp); 093 } 094 // for (int i = 0; i < searchParameters.size(); i++) { 095 // SearchParameter next = searchParameters.get(i); 096 // // next. 097 // } 098 099 /* 100 * Only compartment searching methods may have an ID parameter 101 */ 102 if (isBlank(myCompartmentName) && myIdParamIndex != null) { 103 String msg = theContext 104 .getLocalizer() 105 .getMessage( 106 getClass().getName() + ".idWithoutCompartment", 107 theMethod.getName(), 108 theMethod.getDeclaringClass()); 109 throw new ConfigurationException(Msg.code(1443) + msg); 110 } 111 } 112 113 public String getDescription() { 114 return myDescription; 115 } 116 117 @Override 118 protected BundleTypeEnum getResponseBundleType() { 119 return BundleTypeEnum.SEARCHSET; 120 } 121 122 @Override 123 public RestOperationTypeEnum getRestOperationType() { 124 return RestOperationTypeEnum.SEARCH_TYPE; 125 } 126 127 @Override 128 public ReturnTypeEnum getReturnType() { 129 return ReturnTypeEnum.BUNDLE; 130 } 131 132 @Override 133 public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { 134 assert (myQueryName == null 135 || ((theArgs != null ? theArgs.length : 0) 136 == getParameters().size())) 137 : "Wrong number of arguments: " + (theArgs != null ? theArgs.length : "null"); 138 139 Map<String, List<String>> queryStringArgs = new LinkedHashMap<String, List<String>>(); 140 141 if (myQueryName != null) { 142 queryStringArgs.put(Constants.PARAM_QUERY, Collections.singletonList(myQueryName)); 143 } 144 145 IIdType id = (IIdType) (myIdParamIndex != null ? theArgs[myIdParamIndex] : null); 146 147 String resourceName = getResourceName(); 148 if (theArgs != null) { 149 for (int idx = 0; idx < theArgs.length; idx++) { 150 IParameter nextParam = getParameters().get(idx); 151 nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], queryStringArgs, null); 152 } 153 } 154 155 BaseHttpClientInvocation retVal = 156 createSearchInvocation(getContext(), resourceName, queryStringArgs, id, myCompartmentName, null); 157 158 return retVal; 159 } 160 161 @Override 162 protected boolean isAddContentLocationHeader() { 163 return false; 164 } 165 166 @Override 167 public String toString() { 168 return getMethod().toString(); 169 } 170 171 public static BaseHttpClientInvocation createSearchInvocation( 172 FhirContext theContext, 173 String theSearchUrl, 174 UrlSourceEnum theUrlSource, 175 Map<String, List<String>> theParams) { 176 return new HttpGetClientInvocation(theContext, theParams, theUrlSource, theSearchUrl); 177 } 178 179 public static BaseHttpClientInvocation createSearchInvocation( 180 FhirContext theContext, 181 String theResourceName, 182 Map<String, List<String>> theParameters, 183 IIdType theId, 184 String theCompartmentName, 185 SearchStyleEnum theSearchStyle) { 186 SearchStyleEnum searchStyle = theSearchStyle; 187 if (searchStyle == null) { 188 int length = 0; 189 for (Entry<String, List<String>> nextEntry : theParameters.entrySet()) { 190 length += nextEntry.getKey().length(); 191 for (String next : nextEntry.getValue()) { 192 length += next.length(); 193 } 194 } 195 196 if (length < 5000) { 197 searchStyle = SearchStyleEnum.GET; 198 } else { 199 searchStyle = SearchStyleEnum.POST; 200 } 201 } 202 203 BaseHttpClientInvocation invocation; 204 205 boolean compartmentSearch = false; 206 if (theCompartmentName != null) { 207 if (theId == null || !theId.hasIdPart()) { 208 String msg = theContext 209 .getLocalizer() 210 .getMessage(SearchMethodBinding.class.getName() + ".idNullForCompartmentSearch"); 211 throw new InvalidRequestException(Msg.code(1444) + msg); 212 } 213 compartmentSearch = true; 214 } 215 216 /* 217 * Are we doing a get (GET [base]/Patient?name=foo) or a get with search (GET [base]/Patient/_search?name=foo) or a post (POST [base]/Patient with parameters in the POST body) 218 */ 219 switch (searchStyle) { 220 case GET: 221 default: 222 if (compartmentSearch) { 223 invocation = new HttpGetClientInvocation( 224 theContext, theParameters, theResourceName, theId.getIdPart(), theCompartmentName); 225 } else { 226 invocation = new HttpGetClientInvocation(theContext, theParameters, theResourceName); 227 } 228 break; 229 case GET_WITH_SEARCH: 230 if (compartmentSearch) { 231 invocation = new HttpGetClientInvocation( 232 theContext, 233 theParameters, 234 theResourceName, 235 theId.getIdPart(), 236 theCompartmentName, 237 Constants.PARAM_SEARCH); 238 } else { 239 invocation = new HttpGetClientInvocation( 240 theContext, theParameters, theResourceName, Constants.PARAM_SEARCH); 241 } 242 break; 243 case POST: 244 if (compartmentSearch) { 245 invocation = new HttpPostClientInvocation( 246 theContext, 247 theParameters, 248 theResourceName, 249 theId.getIdPart(), 250 theCompartmentName, 251 Constants.PARAM_SEARCH); 252 } else { 253 invocation = new HttpPostClientInvocation( 254 theContext, theParameters, theResourceName, Constants.PARAM_SEARCH); 255 } 256 } 257 258 return invocation; 259 } 260}