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