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 IPhoneticEncoder myPhoneticEncoder; 062 063 /** 064 * Constructor 065 */ 066 public RuntimeSearchParam( 067 IIdType theId, 068 String theUri, 069 String theName, 070 String theDescription, 071 String thePath, 072 RestSearchParameterTypeEnum theParamType, 073 Set<String> theProvidesMembershipInCompartments, 074 Set<String> theTargets, 075 RuntimeSearchParamStatusEnum theStatus, 076 Collection<String> theBase) { 077 this( 078 theId, 079 theUri, 080 theName, 081 theDescription, 082 thePath, 083 theParamType, 084 theProvidesMembershipInCompartments, 085 theTargets, 086 theStatus, 087 null, 088 Collections.emptyList(), 089 theBase); 090 } 091 092 /** 093 * Copy constructor 094 */ 095 public RuntimeSearchParam(RuntimeSearchParam theSp) { 096 this( 097 theSp.getId(), 098 theSp.getUri(), 099 theSp.getName(), 100 theSp.getDescription(), 101 theSp.getPath(), 102 theSp.getParamType(), 103 theSp.getProvidesMembershipInCompartments(), 104 theSp.getTargets(), 105 theSp.getStatus(), 106 theSp.getComboSearchParamType(), 107 theSp.getComponents(), 108 theSp.getBase()); 109 } 110 111 /** 112 * Constructor 113 */ 114 public RuntimeSearchParam( 115 IIdType theId, 116 String theUri, 117 String theName, 118 String theDescription, 119 String thePath, 120 RestSearchParameterTypeEnum theParamType, 121 Set<String> theProvidesMembershipInCompartments, 122 Set<String> theTargets, 123 RuntimeSearchParamStatusEnum theStatus, 124 ComboSearchParamType theComboSearchParamType, 125 List<Component> theComponents, 126 Collection<String> theBase) { 127 super(); 128 129 myId = theId; 130 myUri = theUri; 131 myName = theName; 132 myDescription = theDescription; 133 myPath = thePath; 134 myParamType = theParamType; 135 myStatus = theStatus; 136 if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) { 137 myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments); 138 } else { 139 myProvidesMembershipInCompartments = null; 140 } 141 if (theTargets != null && theTargets.isEmpty() == false) { 142 myTargets = Collections.unmodifiableSet(theTargets); 143 } else { 144 myTargets = Collections.emptySet(); 145 } 146 147 if (theBase == null || theBase.isEmpty()) { 148 HashSet<String> base = new HashSet<>(); 149 if (isNotBlank(thePath)) { 150 int indexOf = thePath.indexOf('.'); 151 if (indexOf != -1) { 152 base.add(trim(thePath.substring(0, indexOf))); 153 } 154 } 155 myBase = Collections.unmodifiableSet(base); 156 } else { 157 myBase = Collections.unmodifiableSet(new HashSet<>(theBase)); 158 } 159 myComboSearchParamType = theComboSearchParamType; 160 if (theComponents != null) { 161 myComponents = Collections.unmodifiableList(theComponents); 162 } else { 163 myComponents = Collections.emptyList(); 164 } 165 } 166 167 public List<Component> getComponents() { 168 return myComponents; 169 } 170 171 /** 172 * Returns <code>null</code> if this is not a combo search param type 173 */ 174 @Nullable 175 public ComboSearchParamType getComboSearchParamType() { 176 return myComboSearchParamType; 177 } 178 179 /** 180 * Retrieve user data - This can be used to store any application-specific data 181 */ 182 @Nonnull 183 public List<IBaseExtension<?, ?>> getExtensions(String theKey) { 184 List<IBaseExtension<?, ?>> retVal = myExtensions.get(theKey); 185 if (retVal != null) { 186 retVal = Collections.unmodifiableList(retVal); 187 } else { 188 retVal = Collections.emptyList(); 189 } 190 return retVal; 191 } 192 193 /** 194 * Sets user data - This can be used to store any application-specific data 195 */ 196 public RuntimeSearchParam addExtension(String theKey, IBaseExtension<?, ?> theValue) { 197 List<IBaseExtension<?, ?>> valuesList = myExtensions.computeIfAbsent(theKey, k -> new ArrayList<>()); 198 valuesList.add(theValue); 199 return this; 200 } 201 202 @Override 203 public String toString() { 204 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 205 .append("base", myBase) 206 .append("name", myName) 207 .append("path", myPath) 208 .append("id", myId) 209 .append("uri", myUri) 210 .toString(); 211 } 212 213 public IIdType getId() { 214 return myId; 215 } 216 217 public String getUri() { 218 return myUri; 219 } 220 221 @Override 222 public boolean equals(Object theO) { 223 if (this == theO) return true; 224 225 if (theO == null || getClass() != theO.getClass()) return false; 226 227 RuntimeSearchParam that = (RuntimeSearchParam) theO; 228 229 return new EqualsBuilder() 230 .append(getId(), that.getId()) 231 .append(getName(), that.getName()) 232 .append(getPath(), that.getPath()) 233 .append(getUri(), that.getUri()) 234 .isEquals(); 235 } 236 237 @Override 238 public int hashCode() { 239 return new HashCodeBuilder(17, 37) 240 .append(getId()) 241 .append(getName()) 242 .append(getPath()) 243 .append(getUri()) 244 .toHashCode(); 245 } 246 247 public Set<String> getBase() { 248 return myBase; 249 } 250 251 @Nonnull 252 public Set<String> getTargets() { 253 return myTargets; 254 } 255 256 public boolean hasTargets() { 257 return !myTargets.isEmpty(); 258 } 259 260 public RuntimeSearchParamStatusEnum getStatus() { 261 return myStatus; 262 } 263 264 public String getDescription() { 265 return myDescription; 266 } 267 268 public String getName() { 269 return myName; 270 } 271 272 public RestSearchParameterTypeEnum getParamType() { 273 return myParamType; 274 } 275 276 public String getPath() { 277 return myPath; 278 } 279 280 public List<String> getPathsSplit() { 281 return getPathsSplitForResourceType(null); 282 } 283 284 /** 285 * Can return null 286 */ 287 public Set<String> getProvidesMembershipInCompartments() { 288 return myProvidesMembershipInCompartments; 289 } 290 291 public RuntimeSearchParam setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { 292 myPhoneticEncoder = thePhoneticEncoder; 293 return this; 294 } 295 296 public String encode(String theString) { 297 if (myPhoneticEncoder == null || theString == null) { 298 return theString; 299 } 300 return myPhoneticEncoder.encode(theString); 301 } 302 303 public List<String> getPathsSplitForResourceType(@Nullable String theResourceName) { 304 String path = getPath(); 305 if (path.indexOf('|') == -1) { 306 if (theResourceName != null && !pathMatchesResourceType(theResourceName, path)) { 307 return Collections.emptyList(); 308 } 309 return Collections.singletonList(path); 310 } 311 312 List<String> retVal = new ArrayList<>(); 313 StringTokenizer tok = new StringTokenizer(path, "|"); 314 while (tok.hasMoreElements()) { 315 String nextPath = tok.nextToken().trim(); 316 if (theResourceName != null && !pathMatchesResourceType(theResourceName, nextPath)) { 317 continue; 318 } 319 retVal.add(nextPath.trim()); 320 } 321 return retVal; 322 } 323 324 public void addUpliftRefchain(@Nonnull String theCode, @Nonnull String theElementName) { 325 myUpliftRefchains.put(theCode, theElementName); 326 } 327 328 /** 329 * Does this search parameter have an uplift refchain definition for the given code? 330 * See the HAPI FHIR documentation for a description of how uplift refchains work. 331 * 332 * @since 6.6.0 333 */ 334 public boolean hasUpliftRefchain(String theCode) { 335 return myUpliftRefchains.containsKey(theCode); 336 } 337 338 /** 339 * Returns a set of all codes associated with uplift refchains for this search parameter. 340 * See the HAPI FHIR documentation for a description of how uplift refchains work. 341 * 342 * @since 6.6.0 343 */ 344 public Set<String> getUpliftRefchainCodes() { 345 return Collections.unmodifiableSet(myUpliftRefchains.keySet()); 346 } 347 348 /** 349 * Does this search parameter have any uplift refchain definitions? 350 * See the HAPI FHIR documentation for a description of how uplift refchains work. 351 * 352 * @since 6.6.0 353 */ 354 public boolean hasUpliftRefchains() { 355 return !myUpliftRefchains.isEmpty(); 356 } 357 358 public enum RuntimeSearchParamStatusEnum { 359 ACTIVE, 360 DRAFT, 361 RETIRED, 362 UNKNOWN 363 } 364 365 /** 366 * This method tests whether a given FHIRPath expression <i>could</i> 367 * possibly apply to the given resource type. 368 * 369 * @param theResourceName 370 * @param thePath 371 * @return 372 */ 373 static boolean pathMatchesResourceType(String theResourceName, String thePath) { 374 for (int i = 0; i < thePath.length() - 1; i++) { 375 char nextChar = thePath.charAt(i); 376 if (Character.isLowerCase(nextChar)) { 377 return true; 378 } 379 if (Character.isLetter(nextChar)) { 380 if (fhirPathExpressionStartsWith(theResourceName, thePath, i)) { 381 return true; 382 } 383 if (fhirPathExpressionStartsWith("Resource", thePath, i)) { 384 return true; 385 } 386 if (fhirPathExpressionStartsWith("DomainResource", thePath, i)) { 387 return true; 388 } 389 return false; 390 } 391 } 392 393 return false; 394 } 395 396 private static boolean fhirPathExpressionStartsWith(String theResourceName, String thePath, int theStartingIndex) { 397 if (thePath.startsWith(theResourceName, theStartingIndex) && thePath.length() > theResourceName.length()) { 398 for (int i = theResourceName.length() + theStartingIndex; i < thePath.length(); i++) { 399 char nextChar = thePath.charAt(i); 400 if (nextChar == '.') { 401 return true; 402 } else if (nextChar != ' ') { 403 return false; 404 } 405 } 406 } 407 return false; 408 } 409 410 public static class Component { 411 private final String myExpression; 412 private final String myReference; 413 414 /** 415 * Constructor 416 */ 417 public Component(String theExpression, String theReference) { 418 myExpression = theExpression; 419 myReference = theReference; 420 } 421 422 @Override 423 public String toString() { 424 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 425 .append("expression", myExpression) 426 .append("reference", myReference) 427 .toString(); 428 } 429 430 public String getExpression() { 431 return myExpression; 432 } 433 434 public String getReference() { 435 return myReference; 436 } 437 } 438}