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