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