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}