001package ca.uhn.fhir.rest.client.method; 002 003/* 004 * #%L 005 * HAPI FHIR - Client 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<Class<?>, RestSearchParameterTypeEnum>(); 051 ourParamQualifiers = new HashMap<RestSearchParameterTypeEnum, Set<String>>(); 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 101 private List<Class<? extends IQueryParameterType>> myCompositeTypes = Collections.emptyList(); 102 private List<Class<? extends IBaseResource>> myDeclaredTypes; 103 private String myDescription; 104 private String myName; 105 private IParamBinder<?> myParamBinder; 106 private RestSearchParameterTypeEnum myParamType; 107 private Set<String> myQualifierBlacklist; 108 private Set<String> myQualifierWhitelist; 109 private boolean myRequired; 110 private Class<?> myType; 111 112 public SearchParameter() { 113 } 114 115 public SearchParameter(String theName, boolean theRequired) { 116 this.myName = theName; 117 this.myRequired = theRequired; 118 } 119 120 /* 121 * (non-Javadoc) 122 * 123 * @see ca.uhn.fhir.rest.param.IParameter#encode(java.lang.Object) 124 */ 125 @Override 126 public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException { 127 ArrayList<QualifiedParamList> retVal = new ArrayList<QualifiedParamList>(); 128 129 // TODO: declaring method should probably have a generic type.. 130 @SuppressWarnings("rawtypes") 131 IParamBinder paramBinder = myParamBinder; 132 133 @SuppressWarnings("unchecked") 134 List<IQueryParameterOr<?>> val = paramBinder.encode(theContext, theObject); 135 for (IQueryParameterOr<?> nextOr : val) { 136 retVal.add(new QualifiedParamList(nextOr, theContext)); 137 } 138 139 return retVal; 140 } 141 142 public List<Class<? extends IBaseResource>> getDeclaredTypes() { 143 return Collections.unmodifiableList(myDeclaredTypes); 144 } 145 146 public String getDescription() { 147 return myDescription; 148 } 149 150 /* 151 * (non-Javadoc) 152 * 153 * @see ca.uhn.fhir.rest.param.IParameter#getName() 154 */ 155 @Override 156 public String getName() { 157 return myName; 158 } 159 160 @Override 161 public RestSearchParameterTypeEnum getParamType() { 162 return myParamType; 163 } 164 165 public Class<?> getType() { 166 return myType; 167 } 168 169 @Override 170 public boolean handlesMissing() { 171 return false; 172 } 173 174 @Override 175 public boolean isRequired() { 176 return myRequired; 177 } 178 179 /* 180 * (non-Javadoc) 181 * 182 * @see ca.uhn.fhir.rest.param.IParameter#parse(java.util.List) 183 */ 184 @Override 185 public Object parse(FhirContext theContext, List<QualifiedParamList> theString) throws InternalErrorException, InvalidRequestException { 186 return myParamBinder.parse(theContext, getName(), theString); 187 } 188 189 public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) { 190 myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length); 191 myQualifierWhitelist.add(QUALIFIER_ANY_TYPE); 192 193 for (int i = 0; i < theChainWhitelist.length; i++) { 194 if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) { 195 myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY); 196 } else if (theChainWhitelist[i].equals(EMPTY_STRING)) { 197 myQualifierWhitelist.add("."); 198 } else { 199 myQualifierWhitelist.add('.' + theChainWhitelist[i]); 200 } 201 } 202 203 if (theChainBlacklist.length > 0) { 204 myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length); 205 for (String next : theChainBlacklist) { 206 if (next.equals(EMPTY_STRING)) { 207 myQualifierBlacklist.add(EMPTY_STRING); 208 } else { 209 myQualifierBlacklist.add('.' + next); 210 } 211 } 212 } 213 } 214 215 public void setCompositeTypes(Class<? extends IQueryParameterType>[] theCompositeTypes) { 216 myCompositeTypes = Arrays.asList(theCompositeTypes); 217 } 218 219 public void setDeclaredTypes(Class<? extends IBaseResource>[] theTypes) { 220 myDeclaredTypes = Arrays.asList(theTypes); 221 } 222 223 public void setDescription(String theDescription) { 224 myDescription = theDescription; 225 } 226 227 public void setName(String name) { 228 this.myName = name; 229 } 230 231 public void setRequired(boolean required) { 232 this.myRequired = required; 233 } 234 235 @SuppressWarnings("unchecked") 236 public void setType(FhirContext theContext, final Class<?> type, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) { 237 238 this.myType = type; 239 if (IQueryParameterType.class.isAssignableFrom(type)) { 240 myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type, myCompositeTypes); 241 } else if (IQueryParameterOr.class.isAssignableFrom(type)) { 242 myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) type, myCompositeTypes); 243 } else if (IQueryParameterAnd.class.isAssignableFrom(type)) { 244 myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) type, myCompositeTypes); 245 } else if (String.class.equals(type)) { 246 myParamBinder = new StringBinder(); 247 myParamType = RestSearchParameterTypeEnum.STRING; 248 } else if (Date.class.equals(type)) { 249 myParamBinder = new DateBinder(); 250 myParamType = RestSearchParameterTypeEnum.DATE; 251 } else if (Calendar.class.equals(type)) { 252 myParamBinder = new CalendarBinder(); 253 myParamType = RestSearchParameterTypeEnum.DATE; 254 } else if (IPrimitiveType.class.isAssignableFrom(type) && ReflectionUtil.isInstantiable(type)) { 255 RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) type); 256 if (def.getNativeType() != null) { 257 if (def.getNativeType().equals(Date.class)) { 258 myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type); 259 myParamType = RestSearchParameterTypeEnum.DATE; 260 } else if (def.getNativeType().equals(String.class)) { 261 myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type); 262 myParamType = RestSearchParameterTypeEnum.STRING; 263 } 264 } 265 } else { 266 throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName()); 267 } 268 269 RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(type); 270 if (typeEnum != null) { 271 Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum); 272 if (builtInQualifiers != null) { 273 if (myQualifierWhitelist != null) { 274 HashSet<String> qualifierWhitelist = new HashSet<String>(); 275 qualifierWhitelist.addAll(myQualifierWhitelist); 276 qualifierWhitelist.addAll(builtInQualifiers); 277 myQualifierWhitelist = qualifierWhitelist; 278 } else { 279 myQualifierWhitelist = Collections.unmodifiableSet(builtInQualifiers); 280 } 281 } 282 } 283 284 if (myParamType == null) { 285 myParamType = typeEnum; 286 } 287 288 if (myParamType != null) { 289 // ok 290 } else if (StringDt.class.isAssignableFrom(type)) { 291 myParamType = RestSearchParameterTypeEnum.STRING; 292 } else if (BaseIdentifierDt.class.isAssignableFrom(type)) { 293 myParamType = RestSearchParameterTypeEnum.TOKEN; 294 } else if (BaseQuantityDt.class.isAssignableFrom(type)) { 295 myParamType = RestSearchParameterTypeEnum.QUANTITY; 296 } else if (ReferenceParam.class.isAssignableFrom(type)) { 297 myParamType = RestSearchParameterTypeEnum.REFERENCE; 298 } else if (HasParam.class.isAssignableFrom(type)) { 299 myParamType = RestSearchParameterTypeEnum.STRING; 300 } else { 301 throw new ConfigurationException("Unknown search parameter type: " + type); 302 } 303 304 // NB: Once this is enabled, we should return true from handlesMissing if 305 // it's a collection type 306 // if (theInnerCollectionType != null) { 307 // this.parser = new CollectionBinder(this.parser, theInnerCollectionType); 308 // } 309 // 310 // if (theOuterCollectionType != null) { 311 // this.parser = new CollectionBinder(this.parser, theOuterCollectionType); 312 // } 313 314 } 315 316 @Override 317 public String toString() { 318 ToStringBuilder retVal = new ToStringBuilder(this); 319 retVal.append("name", myName); 320 retVal.append("required", myRequired); 321 return retVal.toString(); 322 } 323 324}