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}