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