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