001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context; 021 022import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; 023import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 024import jakarta.annotation.Nonnull; 025import jakarta.annotation.Nullable; 026import org.apache.commons.lang3.builder.EqualsBuilder; 027import org.apache.commons.lang3.builder.HashCodeBuilder; 028import org.apache.commons.lang3.builder.ToStringBuilder; 029import org.apache.commons.lang3.builder.ToStringStyle; 030import org.hl7.fhir.instance.model.api.IBaseExtension; 031import org.hl7.fhir.instance.model.api.IIdType; 032 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041import java.util.StringTokenizer; 042 043import static org.apache.commons.lang3.StringUtils.isNotBlank; 044import static org.apache.commons.lang3.StringUtils.trim; 045 046public class RuntimeSearchParam { 047 private final IIdType myId; 048 private final Set<String> myBase; 049 private final String myDescription; 050 private final String myName; 051 private final RestSearchParameterTypeEnum myParamType; 052 private final String myPath; 053 private final Set<String> myTargets; 054 private final Set<String> myProvidesMembershipInCompartments; 055 private final RuntimeSearchParamStatusEnum myStatus; 056 private final String myUri; 057 private final Map<String, List<IBaseExtension<?, ?>>> myExtensions = new HashMap<>(); 058 private final Map<String, String> myUpliftRefchains = new HashMap<>(); 059 private final ComboSearchParamType myComboSearchParamType; 060 private final List<Component> myComponents; 061 private final IIdType myIdUnqualifiedVersionless; 062 private IPhoneticEncoder myPhoneticEncoder; 063 064 /** 065 * Constructor 066 */ 067 public RuntimeSearchParam( 068 IIdType theId, 069 String theUri, 070 String theName, 071 String theDescription, 072 String thePath, 073 RestSearchParameterTypeEnum theParamType, 074 Set<String> theProvidesMembershipInCompartments, 075 Set<String> theTargets, 076 RuntimeSearchParamStatusEnum theStatus, 077 Collection<String> theBase) { 078 this( 079 theId, 080 theUri, 081 theName, 082 theDescription, 083 thePath, 084 theParamType, 085 theProvidesMembershipInCompartments, 086 theTargets, 087 theStatus, 088 null, 089 Collections.emptyList(), 090 theBase); 091 } 092 093 /** 094 * Copy constructor 095 */ 096 public RuntimeSearchParam(RuntimeSearchParam theSp) { 097 this( 098 theSp.getId(), 099 theSp.getUri(), 100 theSp.getName(), 101 theSp.getDescription(), 102 theSp.getPath(), 103 theSp.getParamType(), 104 theSp.getProvidesMembershipInCompartments(), 105 theSp.getTargets(), 106 theSp.getStatus(), 107 theSp.getComboSearchParamType(), 108 theSp.getComponents(), 109 theSp.getBase()); 110 } 111 112 /** 113 * Constructor 114 */ 115 public RuntimeSearchParam( 116 IIdType theId, 117 String theUri, 118 String theName, 119 String theDescription, 120 String thePath, 121 RestSearchParameterTypeEnum theParamType, 122 Set<String> theProvidesMembershipInCompartments, 123 Set<String> theTargets, 124 RuntimeSearchParamStatusEnum theStatus, 125 ComboSearchParamType theComboSearchParamType, 126 List<Component> theComponents, 127 Collection<String> theBase) { 128 super(); 129 130 myId = theId; 131 myIdUnqualifiedVersionless = theId != null ? theId.toUnqualifiedVersionless() : null; 132 myUri = theUri; 133 myName = theName; 134 myDescription = theDescription; 135 myPath = thePath; 136 myParamType = theParamType; 137 myStatus = theStatus; 138 if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) { 139 myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments); 140 } else { 141 myProvidesMembershipInCompartments = null; 142 } 143 if (theTargets != null && theTargets.isEmpty() == false) { 144 myTargets = Collections.unmodifiableSet(theTargets); 145 } else { 146 myTargets = Collections.emptySet(); 147 } 148 149 if (theBase == null || theBase.isEmpty()) { 150 HashSet<String> base = new HashSet<>(); 151 if (isNotBlank(thePath)) { 152 int indexOf = thePath.indexOf('.'); 153 if (indexOf != -1) { 154 base.add(trim(thePath.substring(0, indexOf))); 155 } 156 } 157 myBase = Collections.unmodifiableSet(base); 158 } else { 159 myBase = Collections.unmodifiableSet(new HashSet<>(theBase)); 160 } 161 myComboSearchParamType = theComboSearchParamType; 162 if (theComponents != null) { 163 myComponents = Collections.unmodifiableList(theComponents); 164 } else { 165 myComponents = Collections.emptyList(); 166 } 167 } 168 169 public List<Component> getComponents() { 170 return myComponents; 171 } 172 173 /** 174 * Returns <code>null</code> if this is not a combo search param type 175 */ 176 @Nullable 177 public ComboSearchParamType getComboSearchParamType() { 178 return myComboSearchParamType; 179 } 180 181 /** 182 * Retrieve user data - This can be used to store any application-specific data 183 */ 184 @Nonnull 185 public List<IBaseExtension<?, ?>> getExtensions(String theKey) { 186 List<IBaseExtension<?, ?>> retVal = myExtensions.get(theKey); 187 if (retVal != null) { 188 retVal = Collections.unmodifiableList(retVal); 189 } else { 190 retVal = Collections.emptyList(); 191 } 192 return retVal; 193 } 194 195 /** 196 * Sets user data - This can be used to store any application-specific data 197 */ 198 public RuntimeSearchParam addExtension(String theKey, IBaseExtension<?, ?> theValue) { 199 List<IBaseExtension<?, ?>> valuesList = myExtensions.computeIfAbsent(theKey, k -> new ArrayList<>()); 200 valuesList.add(theValue); 201 return this; 202 } 203 204 @Override 205 public String toString() { 206 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 207 .append("base", myBase) 208 .append("name", myName) 209 .append("path", myPath) 210 .append("id", myId) 211 .append("uri", myUri) 212 .toString(); 213 } 214 215 public IIdType getId() { 216 return myId; 217 } 218 219 public IIdType getIdUnqualifiedVersionless() { 220 return myIdUnqualifiedVersionless; 221 } 222 223 public String getUri() { 224 return myUri; 225 } 226 227 @Override 228 public boolean equals(Object theO) { 229 if (this == theO) return true; 230 231 if (theO == null || getClass() != theO.getClass()) return false; 232 233 RuntimeSearchParam that = (RuntimeSearchParam) theO; 234 235 return new EqualsBuilder() 236 .append(getId(), that.getId()) 237 .append(getName(), that.getName()) 238 .append(getPath(), that.getPath()) 239 .append(getUri(), that.getUri()) 240 .isEquals(); 241 } 242 243 @Override 244 public int hashCode() { 245 return new HashCodeBuilder(17, 37) 246 .append(getId()) 247 .append(getName()) 248 .append(getPath()) 249 .append(getUri()) 250 .toHashCode(); 251 } 252 253 public Set<String> getBase() { 254 return myBase; 255 } 256 257 @Nonnull 258 public Set<String> getTargets() { 259 return myTargets; 260 } 261 262 public boolean hasTargets() { 263 return !myTargets.isEmpty(); 264 } 265 266 public RuntimeSearchParamStatusEnum getStatus() { 267 return myStatus; 268 } 269 270 public String getDescription() { 271 return myDescription; 272 } 273 274 public String getName() { 275 return myName; 276 } 277 278 public RestSearchParameterTypeEnum getParamType() { 279 return myParamType; 280 } 281 282 public String getPath() { 283 return myPath; 284 } 285 286 public List<String> getPathsSplit() { 287 return getPathsSplitForResourceType(null); 288 } 289 290 /** 291 * Can return null 292 */ 293 public Set<String> getProvidesMembershipInCompartments() { 294 return myProvidesMembershipInCompartments; 295 } 296 297 public RuntimeSearchParam setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { 298 myPhoneticEncoder = thePhoneticEncoder; 299 return this; 300 } 301 302 public String encode(String theString) { 303 if (myPhoneticEncoder == null || theString == null) { 304 return theString; 305 } 306 return myPhoneticEncoder.encode(theString); 307 } 308 309 public List<String> getPathsSplitForResourceType(@Nullable String theResourceName) { 310 String path = getPath(); 311 if (path.indexOf('|') == -1) { 312 if (theResourceName != null && !pathMatchesResourceType(theResourceName, path)) { 313 return Collections.emptyList(); 314 } 315 return Collections.singletonList(path); 316 } 317 318 List<String> retVal = new ArrayList<>(); 319 StringTokenizer tok = new StringTokenizer(path, "|"); 320 while (tok.hasMoreElements()) { 321 String nextPath = tok.nextToken().trim(); 322 if (theResourceName != null && !pathMatchesResourceType(theResourceName, nextPath)) { 323 continue; 324 } 325 retVal.add(nextPath.trim()); 326 } 327 return retVal; 328 } 329 330 public void addUpliftRefchain(@Nonnull String theCode, @Nonnull String theElementName) { 331 myUpliftRefchains.put(theCode, theElementName); 332 } 333 334 /** 335 * Does this search parameter have an uplift refchain definition for the given code? 336 * See the HAPI FHIR documentation for a description of how uplift refchains work. 337 * 338 * @since 6.6.0 339 */ 340 public boolean hasUpliftRefchain(String theCode) { 341 return myUpliftRefchains.containsKey(theCode); 342 } 343 344 /** 345 * Returns a set of all codes associated with uplift refchains for this search parameter. 346 * See the HAPI FHIR documentation for a description of how uplift refchains work. 347 * 348 * @since 6.6.0 349 */ 350 public Set<String> getUpliftRefchainCodes() { 351 return Collections.unmodifiableSet(myUpliftRefchains.keySet()); 352 } 353 354 /** 355 * Does this search parameter have any uplift refchain definitions? 356 * See the HAPI FHIR documentation for a description of how uplift refchains work. 357 * 358 * @since 6.6.0 359 */ 360 public boolean hasUpliftRefchains() { 361 return !myUpliftRefchains.isEmpty(); 362 } 363 364 public enum RuntimeSearchParamStatusEnum { 365 ACTIVE, 366 DRAFT, 367 RETIRED, 368 UNKNOWN 369 } 370 371 /** 372 * This method tests whether a given FHIRPath expression <i>could</i> 373 * possibly apply to the given resource type. 374 * 375 * @param theResourceName 376 * @param thePath 377 * @return 378 */ 379 static boolean pathMatchesResourceType(String theResourceName, String thePath) { 380 for (int i = 0; i < thePath.length() - 1; i++) { 381 char nextChar = thePath.charAt(i); 382 if (Character.isLowerCase(nextChar)) { 383 return true; 384 } 385 if (Character.isLetter(nextChar)) { 386 if (fhirPathExpressionStartsWith(theResourceName, thePath, i)) { 387 return true; 388 } 389 if (fhirPathExpressionStartsWith("Resource", thePath, i)) { 390 return true; 391 } 392 if (fhirPathExpressionStartsWith("DomainResource", thePath, i)) { 393 return true; 394 } 395 return false; 396 } 397 } 398 399 return false; 400 } 401 402 private static boolean fhirPathExpressionStartsWith(String theResourceName, String thePath, int theStartingIndex) { 403 if (thePath.startsWith(theResourceName, theStartingIndex) && thePath.length() > theResourceName.length()) { 404 for (int i = theResourceName.length() + theStartingIndex; i < thePath.length(); i++) { 405 char nextChar = thePath.charAt(i); 406 if (nextChar == '.') { 407 return true; 408 } else if (nextChar != ' ') { 409 return false; 410 } 411 } 412 } 413 return false; 414 } 415 416 public static class Component { 417 private final String myExpression; 418 private final String myReference; 419 420 /** 421 * Constructor 422 */ 423 public Component(String theExpression, String theReference) { 424 myExpression = theExpression; 425 myReference = theReference; 426 } 427 428 @Override 429 public String toString() { 430 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 431 .append("expression", myExpression) 432 .append("reference", myReference) 433 .toString(); 434 } 435 436 public String getExpression() { 437 return myExpression; 438 } 439 440 public String getReference() { 441 return myReference; 442 } 443 } 444}