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