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