001package ca.uhn.fhir.rest.server.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2021 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 */
022import java.util.*;
023
024import org.apache.commons.lang3.builder.ToStringBuilder;
025import org.hl7.fhir.instance.model.api.IBaseResource;
026import org.hl7.fhir.instance.model.api.IPrimitiveType;
027
028import ca.uhn.fhir.context.*;
029import ca.uhn.fhir.model.api.*;
030import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
031import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
032import ca.uhn.fhir.model.primitive.StringDt;
033import ca.uhn.fhir.rest.annotation.OptionalParam;
034import ca.uhn.fhir.rest.api.*;
035import ca.uhn.fhir.rest.param.*;
036import ca.uhn.fhir.rest.param.binder.*;
037import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
038import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
039import ca.uhn.fhir.util.CollectionUtil;
040import ca.uhn.fhir.util.ReflectionUtil;
041
042public class SearchParameter extends BaseQueryParameter {
043
044        private static final String EMPTY_STRING = "";
045        private static final HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
046        private static final HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
047        static final String QUALIFIER_ANY_TYPE = ":*";
048
049        static {
050                ourParamTypes = new HashMap<>();
051                ourParamQualifiers = new HashMap<>();
052
053                ourParamTypes.put(StringParam.class, RestSearchParameterTypeEnum.STRING);
054                ourParamTypes.put(StringOrListParam.class, RestSearchParameterTypeEnum.STRING);
055                ourParamTypes.put(StringAndListParam.class, RestSearchParameterTypeEnum.STRING);
056                ourParamQualifiers.put(RestSearchParameterTypeEnum.STRING, CollectionUtil.newSet(Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_STRING_CONTAINS, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
057
058                ourParamTypes.put(UriParam.class, RestSearchParameterTypeEnum.URI);
059                ourParamTypes.put(UriOrListParam.class, RestSearchParameterTypeEnum.URI);
060                ourParamTypes.put(UriAndListParam.class, RestSearchParameterTypeEnum.URI);
061                // TODO: are these right for URI?
062                ourParamQualifiers.put(RestSearchParameterTypeEnum.URI, CollectionUtil.newSet(Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
063
064                ourParamTypes.put(TokenParam.class, RestSearchParameterTypeEnum.TOKEN);
065                ourParamTypes.put(TokenOrListParam.class, RestSearchParameterTypeEnum.TOKEN);
066                ourParamTypes.put(TokenAndListParam.class, RestSearchParameterTypeEnum.TOKEN);
067                ourParamQualifiers.put(RestSearchParameterTypeEnum.TOKEN, CollectionUtil.newSet(Constants.PARAMQUALIFIER_TOKEN_TEXT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
068
069                ourParamTypes.put(DateParam.class, RestSearchParameterTypeEnum.DATE);
070                ourParamTypes.put(DateOrListParam.class, RestSearchParameterTypeEnum.DATE);
071                ourParamTypes.put(DateAndListParam.class, RestSearchParameterTypeEnum.DATE);
072                ourParamTypes.put(DateRangeParam.class, RestSearchParameterTypeEnum.DATE);
073                ourParamQualifiers.put(RestSearchParameterTypeEnum.DATE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
074
075                ourParamTypes.put(QuantityParam.class, RestSearchParameterTypeEnum.QUANTITY);
076                ourParamTypes.put(QuantityOrListParam.class, RestSearchParameterTypeEnum.QUANTITY);
077                ourParamTypes.put(QuantityAndListParam.class, RestSearchParameterTypeEnum.QUANTITY);
078                ourParamQualifiers.put(RestSearchParameterTypeEnum.QUANTITY, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
079
080                ourParamTypes.put(NumberParam.class, RestSearchParameterTypeEnum.NUMBER);
081                ourParamTypes.put(NumberOrListParam.class, RestSearchParameterTypeEnum.NUMBER);
082                ourParamTypes.put(NumberAndListParam.class, RestSearchParameterTypeEnum.NUMBER);
083                ourParamQualifiers.put(RestSearchParameterTypeEnum.NUMBER, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
084
085                ourParamTypes.put(ReferenceParam.class, RestSearchParameterTypeEnum.REFERENCE);
086                ourParamTypes.put(ReferenceOrListParam.class, RestSearchParameterTypeEnum.REFERENCE);
087                ourParamTypes.put(ReferenceAndListParam.class, RestSearchParameterTypeEnum.REFERENCE);
088                // --vvvv-- no empty because that gets added from OptionalParam#chainWhitelist
089                ourParamQualifiers.put(RestSearchParameterTypeEnum.REFERENCE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING));
090
091                ourParamTypes.put(CompositeParam.class, RestSearchParameterTypeEnum.COMPOSITE);
092                ourParamTypes.put(CompositeOrListParam.class, RestSearchParameterTypeEnum.COMPOSITE);
093                ourParamTypes.put(CompositeAndListParam.class, RestSearchParameterTypeEnum.COMPOSITE);
094                ourParamQualifiers.put(RestSearchParameterTypeEnum.COMPOSITE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
095
096                ourParamTypes.put(HasParam.class, RestSearchParameterTypeEnum.HAS);
097                ourParamTypes.put(HasOrListParam.class, RestSearchParameterTypeEnum.HAS);
098                ourParamTypes.put(HasAndListParam.class, RestSearchParameterTypeEnum.HAS);
099
100                ourParamTypes.put(SpecialParam.class, RestSearchParameterTypeEnum.SPECIAL);
101                ourParamTypes.put(SpecialOrListParam.class, RestSearchParameterTypeEnum.SPECIAL);
102                ourParamTypes.put(SpecialAndListParam.class, RestSearchParameterTypeEnum.SPECIAL);
103                ourParamQualifiers.put(RestSearchParameterTypeEnum.SPECIAL, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING));
104
105        }
106
107        private List<Class<? extends IQueryParameterType>> myCompositeTypes = Collections.emptyList();
108        private List<Class<? extends IBaseResource>> myDeclaredTypes;
109        private String myDescription;
110        private String myName;
111        private IParamBinder<?> myParamBinder;
112        private RestSearchParameterTypeEnum myParamType;
113        private Set<String> myQualifierBlacklist;
114        private Set<String> myQualifierWhitelist;
115        private boolean myRequired;
116        private Class<?> myType;
117        private boolean mySupportsRepetition = false;
118
119        public SearchParameter() {
120        }
121
122        public SearchParameter(String theName, boolean theRequired) {
123                this.myName = theName;
124                this.myRequired = theRequired;
125        }
126
127        /*
128         * (non-Javadoc)
129         * 
130         * @see ca.uhn.fhir.rest.param.IParameter#encode(java.lang.Object)
131         */
132        @Override
133        public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException {
134                ArrayList<QualifiedParamList> retVal = new ArrayList<>();
135
136                // TODO: declaring method should probably have a generic type..
137                @SuppressWarnings("rawtypes")
138                IParamBinder paramBinder = myParamBinder;
139
140                @SuppressWarnings("unchecked")
141                List<IQueryParameterOr<?>> val = paramBinder.encode(theContext, theObject);
142                for (IQueryParameterOr<?> nextOr : val) {
143                        retVal.add(new QualifiedParamList(nextOr, theContext));
144                }
145
146                return retVal;
147        }
148
149        public List<Class<? extends IBaseResource>> getDeclaredTypes() {
150                return Collections.unmodifiableList(myDeclaredTypes);
151        }
152
153        public String getDescription() {
154                return myDescription;
155        }
156
157        /*
158         * (non-Javadoc)
159         * 
160         * @see ca.uhn.fhir.rest.param.IParameter#getName()
161         */
162        @Override
163        public String getName() {
164                return myName;
165        }
166
167        @Override
168        public RestSearchParameterTypeEnum getParamType() {
169                return myParamType;
170        }
171
172        @Override
173        public Set<String> getQualifierBlacklist() {
174                return myQualifierBlacklist;
175        }
176
177        @Override
178        public Set<String> getQualifierWhitelist() {
179                return myQualifierWhitelist;
180        }
181
182        public Class<?> getType() {
183                return myType;
184        }
185
186        @Override
187        public boolean handlesMissing() {
188                return false;
189        }
190
191        @Override
192        public boolean isRequired() {
193                return myRequired;
194        }
195
196        /*
197         * (non-Javadoc)
198         * 
199         * @see ca.uhn.fhir.rest.param.IParameter#parse(java.util.List)
200         */
201        @Override
202        public Object parse(FhirContext theContext, List<QualifiedParamList> theString) throws InternalErrorException, InvalidRequestException {
203                return myParamBinder.parse(theContext, getName(), theString);
204        }
205
206        public void setChainLists(String[] theChainWhitelist, String[] theChainBlacklist) {
207                myQualifierWhitelist = new HashSet<>(theChainWhitelist.length);
208                myQualifierWhitelist.add(QUALIFIER_ANY_TYPE);
209
210                for (String nextChain : theChainWhitelist) {
211                        if (nextChain.equals(OptionalParam.ALLOW_CHAIN_ANY)) {
212                                myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY);
213                        } else if (nextChain.equals(EMPTY_STRING)) {
214                                myQualifierWhitelist.add(".");
215                        } else {
216                                myQualifierWhitelist.add('.' + nextChain);
217                        }
218                }
219
220                if (theChainBlacklist.length > 0) {
221                        myQualifierBlacklist = new HashSet<>(theChainBlacklist.length);
222                        for (String next : theChainBlacklist) {
223                                if (next.equals(EMPTY_STRING)) {
224                                        myQualifierBlacklist.add(EMPTY_STRING);
225                                } else {
226                                        myQualifierBlacklist.add('.' + next);
227                                }
228                        }
229                }
230        }
231
232        public void setCompositeTypes(Class<? extends IQueryParameterType>[] theCompositeTypes) {
233                myCompositeTypes = Arrays.asList(theCompositeTypes);
234        }
235
236        public void setDeclaredTypes(Class<? extends IBaseResource>[] theTypes) {
237                myDeclaredTypes = Arrays.asList(theTypes);
238        }
239
240        public void setDescription(String theDescription) {
241                myDescription = theDescription;
242        }
243
244        public void setName(String name) {
245                this.myName = name;
246        }
247
248        public void setRequired(boolean required) {
249                this.myRequired = required;
250        }
251
252        @Override
253        protected boolean supportsRepetition() {
254                return mySupportsRepetition;
255        }
256
257        @SuppressWarnings("unchecked")
258        public void setType(FhirContext theContext, final Class<?> theType, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) {
259
260                
261                this.myType = theType;
262                if (IQueryParameterType.class.isAssignableFrom(theType)) {
263                        myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) theType, myCompositeTypes);
264                } else if (IQueryParameterOr.class.isAssignableFrom(theType)) {
265                        myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) theType, myCompositeTypes);
266                } else if (IQueryParameterAnd.class.isAssignableFrom(theType)) {
267                        myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) theType, myCompositeTypes);
268                        mySupportsRepetition = true;
269                } else if (String.class.equals(theType)) {
270                        myParamBinder = new StringBinder();
271                        myParamType = RestSearchParameterTypeEnum.STRING;
272                } else if (Date.class.equals(theType)) {
273                        myParamBinder = new DateBinder();
274                        myParamType = RestSearchParameterTypeEnum.DATE;
275                } else if (Calendar.class.equals(theType)) {
276                        myParamBinder = new CalendarBinder();
277                        myParamType = RestSearchParameterTypeEnum.DATE;
278                } else if (IPrimitiveType.class.isAssignableFrom(theType) && ReflectionUtil.isInstantiable(theType)) {
279                        RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) theType);
280                        if (def.getNativeType() != null) {
281                                if (def.getNativeType().equals(Date.class)) {
282                                        myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) theType);
283                                        myParamType = RestSearchParameterTypeEnum.DATE;
284                                } else if (def.getNativeType().equals(String.class)) {
285                                        myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) theType);
286                                        myParamType = RestSearchParameterTypeEnum.STRING;
287                                }
288                        }
289                } else {
290                        throw new ConfigurationException("Unsupported data type for parameter: " + theType.getCanonicalName());
291                }
292
293                RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(theType);
294                if (typeEnum != null) {
295                        Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
296                        if (builtInQualifiers != null) {
297                                if (myQualifierWhitelist != null) {
298                                        HashSet<String> qualifierWhitelist = new HashSet<>();
299                                        qualifierWhitelist.addAll(myQualifierWhitelist);
300                                        qualifierWhitelist.addAll(builtInQualifiers);
301                                        myQualifierWhitelist = qualifierWhitelist;
302                                } else {
303                                        myQualifierWhitelist = Collections.unmodifiableSet(builtInQualifiers);
304                                }
305                        }
306                }
307
308                if (myParamType == null) {
309                        myParamType = typeEnum;
310                }
311
312                if (myParamType != null) {
313                        // ok
314                } else if (StringDt.class.isAssignableFrom(theType)) {
315                        myParamType = RestSearchParameterTypeEnum.STRING;
316                } else if (BaseIdentifierDt.class.isAssignableFrom(theType)) {
317                        myParamType = RestSearchParameterTypeEnum.TOKEN;
318                } else if (BaseQuantityDt.class.isAssignableFrom(theType)) {
319                        myParamType = RestSearchParameterTypeEnum.QUANTITY;
320                } else if (ReferenceParam.class.isAssignableFrom(theType)) {
321                        myParamType = RestSearchParameterTypeEnum.REFERENCE;
322                } else if (HasParam.class.isAssignableFrom(theType)) {
323                        myParamType = RestSearchParameterTypeEnum.STRING;
324                } else {
325                        throw new ConfigurationException("Unknown search parameter type: " + theType);
326                }
327
328                // NB: Once this is enabled, we should return true from handlesMissing if
329                // it's a collection theType
330                // if (theInnerCollectionType != null) {
331                // this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
332                // }
333                //
334                // if (theOuterCollectionType != null) {
335                // this.parser = new CollectionBinder(this.parser, theOuterCollectionType);
336                // }
337
338        }
339
340        @Override
341        public String toString() {
342                ToStringBuilder retVal = new ToStringBuilder(this);
343                retVal.append("name", myName);
344                retVal.append("required", myRequired);
345                return retVal.toString();
346        }
347
348}