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