001package ca.uhn.fhir.rest.client.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Client Framework
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
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 HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
046        private static HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
047        static final String QUALIFIER_ANY_TYPE = ":*";
048
049        static {
050                ourParamTypes = new HashMap<Class<?>, RestSearchParameterTypeEnum>();
051                ourParamQualifiers = new HashMap<RestSearchParameterTypeEnum, Set<String>>();
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
101        private List<Class<? extends IQueryParameterType>> myCompositeTypes = Collections.emptyList();
102        private List<Class<? extends IBaseResource>> myDeclaredTypes;
103        private String myDescription;
104        private String myName;
105        private IParamBinder<?> myParamBinder;
106        private RestSearchParameterTypeEnum myParamType;
107        private Set<String> myQualifierBlacklist;
108        private Set<String> myQualifierWhitelist;
109        private boolean myRequired;
110        private Class<?> myType;
111
112        public SearchParameter() {
113        }
114
115        public SearchParameter(String theName, boolean theRequired) {
116                this.myName = theName;
117                this.myRequired = theRequired;
118        }
119
120        /*
121         * (non-Javadoc)
122         * 
123         * @see ca.uhn.fhir.rest.param.IParameter#encode(java.lang.Object)
124         */
125        @Override
126        public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException {
127                ArrayList<QualifiedParamList> retVal = new ArrayList<QualifiedParamList>();
128
129                // TODO: declaring method should probably have a generic type..
130                @SuppressWarnings("rawtypes")
131                IParamBinder paramBinder = myParamBinder;
132
133                @SuppressWarnings("unchecked")
134                List<IQueryParameterOr<?>> val = paramBinder.encode(theContext, theObject);
135                for (IQueryParameterOr<?> nextOr : val) {
136                        retVal.add(new QualifiedParamList(nextOr, theContext));
137                }
138
139                return retVal;
140        }
141
142        public List<Class<? extends IBaseResource>> getDeclaredTypes() {
143                return Collections.unmodifiableList(myDeclaredTypes);
144        }
145
146        public String getDescription() {
147                return myDescription;
148        }
149
150        /*
151         * (non-Javadoc)
152         * 
153         * @see ca.uhn.fhir.rest.param.IParameter#getName()
154         */
155        @Override
156        public String getName() {
157                return myName;
158        }
159
160        @Override
161        public RestSearchParameterTypeEnum getParamType() {
162                return myParamType;
163        }
164
165        public Class<?> getType() {
166                return myType;
167        }
168
169        @Override
170        public boolean handlesMissing() {
171                return false;
172        }
173
174        @Override
175        public boolean isRequired() {
176                return myRequired;
177        }
178
179        /*
180         * (non-Javadoc)
181         * 
182         * @see ca.uhn.fhir.rest.param.IParameter#parse(java.util.List)
183         */
184        @Override
185        public Object parse(FhirContext theContext, List<QualifiedParamList> theString) throws InternalErrorException, InvalidRequestException {
186                return myParamBinder.parse(theContext, getName(), theString);
187        }
188
189        public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) {
190                myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length);
191                myQualifierWhitelist.add(QUALIFIER_ANY_TYPE);
192
193                for (int i = 0; i < theChainWhitelist.length; i++) {
194                        if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) {
195                                myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY);
196                        } else if (theChainWhitelist[i].equals(EMPTY_STRING)) {
197                                myQualifierWhitelist.add(".");
198                        } else {
199                                myQualifierWhitelist.add('.' + theChainWhitelist[i]);
200                        }
201                }
202
203                if (theChainBlacklist.length > 0) {
204                        myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length);
205                        for (String next : theChainBlacklist) {
206                                if (next.equals(EMPTY_STRING)) {
207                                        myQualifierBlacklist.add(EMPTY_STRING);
208                                } else {
209                                        myQualifierBlacklist.add('.' + next);
210                                }
211                        }
212                }
213        }
214
215        public void setCompositeTypes(Class<? extends IQueryParameterType>[] theCompositeTypes) {
216                myCompositeTypes = Arrays.asList(theCompositeTypes);
217        }
218
219        public void setDeclaredTypes(Class<? extends IBaseResource>[] theTypes) {
220                myDeclaredTypes = Arrays.asList(theTypes);
221        }
222
223        public void setDescription(String theDescription) {
224                myDescription = theDescription;
225        }
226
227        public void setName(String name) {
228                this.myName = name;
229        }
230
231        public void setRequired(boolean required) {
232                this.myRequired = required;
233        }
234
235        @SuppressWarnings("unchecked")
236        public void setType(FhirContext theContext, final Class<?> type, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) {
237
238                this.myType = type;
239                if (IQueryParameterType.class.isAssignableFrom(type)) {
240                        myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type, myCompositeTypes);
241                } else if (IQueryParameterOr.class.isAssignableFrom(type)) {
242                        myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) type, myCompositeTypes);
243                } else if (IQueryParameterAnd.class.isAssignableFrom(type)) {
244                        myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) type, myCompositeTypes);
245                } else if (String.class.equals(type)) {
246                        myParamBinder = new StringBinder();
247                        myParamType = RestSearchParameterTypeEnum.STRING;
248                } else if (Date.class.equals(type)) {
249                        myParamBinder = new DateBinder();
250                        myParamType = RestSearchParameterTypeEnum.DATE;
251                } else if (Calendar.class.equals(type)) {
252                        myParamBinder = new CalendarBinder();
253                        myParamType = RestSearchParameterTypeEnum.DATE;
254                } else if (IPrimitiveType.class.isAssignableFrom(type) && ReflectionUtil.isInstantiable(type)) {
255                        RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) type);
256                        if (def.getNativeType() != null) {
257                                if (def.getNativeType().equals(Date.class)) {
258                                        myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type);
259                                        myParamType = RestSearchParameterTypeEnum.DATE;
260                                } else if (def.getNativeType().equals(String.class)) {
261                                        myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type);
262                                        myParamType = RestSearchParameterTypeEnum.STRING;
263                                }
264                        }
265                } else {
266                        throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName());
267                }
268
269                RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(type);
270                if (typeEnum != null) {
271                        Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
272                        if (builtInQualifiers != null) {
273                                if (myQualifierWhitelist != null) {
274                                        HashSet<String> qualifierWhitelist = new HashSet<String>();
275                                        qualifierWhitelist.addAll(myQualifierWhitelist);
276                                        qualifierWhitelist.addAll(builtInQualifiers);
277                                        myQualifierWhitelist = qualifierWhitelist;
278                                } else {
279                                        myQualifierWhitelist = Collections.unmodifiableSet(builtInQualifiers);
280                                }
281                        }
282                }
283
284                if (myParamType == null) {
285                        myParamType = typeEnum;
286                }
287
288                if (myParamType != null) {
289                        // ok
290                } else if (StringDt.class.isAssignableFrom(type)) {
291                        myParamType = RestSearchParameterTypeEnum.STRING;
292                } else if (BaseIdentifierDt.class.isAssignableFrom(type)) {
293                        myParamType = RestSearchParameterTypeEnum.TOKEN;
294                } else if (BaseQuantityDt.class.isAssignableFrom(type)) {
295                        myParamType = RestSearchParameterTypeEnum.QUANTITY;
296                } else if (ReferenceParam.class.isAssignableFrom(type)) {
297                        myParamType = RestSearchParameterTypeEnum.REFERENCE;
298                } else if (HasParam.class.isAssignableFrom(type)) {
299                        myParamType = RestSearchParameterTypeEnum.STRING;
300                } else {
301                        throw new ConfigurationException("Unknown search parameter type: " + type);
302                }
303
304                // NB: Once this is enabled, we should return true from handlesMissing if
305                // it's a collection type
306                // if (theInnerCollectionType != null) {
307                // this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
308                // }
309                //
310                // if (theOuterCollectionType != null) {
311                // this.parser = new CollectionBinder(this.parser, theOuterCollectionType);
312                // }
313
314        }
315
316        @Override
317        public String toString() {
318                ToStringBuilder retVal = new ToStringBuilder(this);
319                retVal.append("name", myName);
320                retVal.append("required", myRequired);
321                return retVal.toString();
322        }
323
324}