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}