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