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