
001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 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.rest.param; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.model.primitive.IdDt; 024import ca.uhn.fhir.rest.api.Constants; 025import ca.uhn.fhir.util.CoverageIgnore; 026import org.apache.commons.lang3.builder.ToStringBuilder; 027import org.apache.commons.lang3.builder.ToStringStyle; 028import org.hl7.fhir.instance.model.api.IBaseResource; 029import org.hl7.fhir.instance.model.api.IIdType; 030 031import java.math.BigDecimal; 032import java.util.Objects; 033 034import static ca.uhn.fhir.model.primitive.IdDt.isValidLong; 035import static org.apache.commons.lang3.StringUtils.isBlank; 036import static org.apache.commons.lang3.StringUtils.isNotBlank; 037 038public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ { 039 040 private String myChain; 041 private String myResourceType; 042 private String myBaseUrl; 043 private String myValue; 044 private String myIdPart; 045 private Boolean myMdmExpand; 046 047 /** 048 * Constructor 049 */ 050 public ReferenceParam() { 051 super(); 052 } 053 054 /** 055 * Constructor 056 */ 057 public ReferenceParam(String theValue) { 058 setValueAsQueryToken(null, null, null, theValue); 059 } 060 061 /** 062 * Constructor 063 */ 064 public ReferenceParam(String theChain, String theValue) { 065 setValueAsQueryToken(null, null, null, theValue); 066 setChain(theChain); 067 } 068 069 /** 070 * Constructor 071 */ 072 public ReferenceParam(String theResourceType, String theChain, String theValue) { 073 String qualifier = ""; 074 if (isNotBlank(theResourceType)) { 075 qualifier = ":" + theResourceType; 076 } 077 if (isNotBlank(theChain)) { 078 qualifier = qualifier + "." + theChain; 079 } 080 081 setValueAsQueryToken(null, null, qualifier, theValue); 082 } 083 084 /** 085 * Constructor 086 * 087 * @since 5.0.0 088 */ 089 public ReferenceParam(IIdType theValue) { 090 if (theValue != null) { 091 setValueAsQueryToken(null, null, null, theValue.getValue()); 092 } 093 } 094 095 @SuppressWarnings("SizeReplaceableByIsEmpty") 096 private String defaultGetQueryParameterQualifier() { 097 StringBuilder b = new StringBuilder(); 098 if (isNotBlank(myChain)) { 099 if (isNotBlank(getResourceType())) { 100 b.append(':'); 101 b.append(getResourceType()); 102 } 103 b.append('.'); 104 b.append(myChain); 105 } 106 if (b.length() > 0) { 107 return b.toString(); 108 } 109 return null; 110 } 111 112 @Override 113 String doGetQueryParameterQualifier() { 114 return this.myMdmExpand != null ? ":mdm" : defaultGetQueryParameterQualifier(); 115 } 116 117 @Override 118 String doGetValueAsQueryToken() { 119 if (isBlank(getResourceType())) { 120 return myValue; // e.g. urn:asdjd or 123 or cid:wieiuru or #1 121 } else { 122 if (isBlank(getChain()) && isNotBlank(getResourceType())) { 123 return getResourceType() + "/" + getIdPart(); 124 } 125 return myValue; 126 } 127 } 128 129 @Override 130 void doSetValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theValue) { 131 if (Constants.PARAMQUALIFIER_MDM.equals(theQualifier)) { 132 myMdmExpand = true; 133 theQualifier = ""; 134 } 135 136 String q = theQualifier; 137 if (isNotBlank(q)) { 138 if (q.startsWith(":")) { 139 int nextIdx = q.indexOf('.'); 140 if (nextIdx != -1) { 141 myChain = q.substring(nextIdx + 1); 142 myResourceType = q.substring(1, nextIdx); 143 } else { 144 myChain = null; 145 myResourceType = q.substring(1); 146 } 147 148 myValue = theValue; 149 myIdPart = theValue; 150 151 IdDt id = new IdDt(theValue); 152 if (!id.hasBaseUrl() && id.hasIdPart() && id.hasResourceType()) { 153 if (id.getResourceType().equals(myResourceType)) { 154 myIdPart = id.getIdPart(); 155 } 156 } 157 158 } else if (q.startsWith(".")) { 159 myChain = q.substring(1); 160 myResourceType = null; 161 myValue = theValue; 162 myIdPart = theValue; 163 } 164 } else { 165 myChain = null; 166 myValue = theValue; 167 IdDt id = new IdDt(theValue); 168 myResourceType = id.getResourceType(); 169 myIdPart = id.getIdPart(); 170 myBaseUrl = id.getBaseUrl(); 171 } 172 } 173 174 @CoverageIgnore 175 public String getBaseUrl() { 176 return myBaseUrl; 177 } 178 179 public boolean isMdmExpand() { 180 return myMdmExpand != null && myMdmExpand; 181 } 182 183 public ReferenceParam setMdmExpand(boolean theMdmExpand) { 184 myMdmExpand = theMdmExpand; 185 return this; 186 } 187 188 /** 189 * Returns <code>true</code> if this parameter has a chain value (i.e. {@link #getChain()} will not return null or an empty string) 190 * 191 * @since 8.6.0 192 */ 193 public boolean hasChain() { 194 return isNotBlank(myChain); 195 } 196 197 public String getChain() { 198 return myChain; 199 } 200 201 public ReferenceParam setChain(String theChain) { 202 myChain = theChain; 203 return this; 204 } 205 206 @CoverageIgnore 207 public String getIdPart() { 208 return myIdPart; 209 } 210 211 @CoverageIgnore 212 public BigDecimal getIdPartAsBigDecimal() { 213 return new IdDt(myValue).getIdPartAsBigDecimal(); 214 } 215 216 @CoverageIgnore 217 public Long getIdPartAsLong() { 218 return new IdDt(myValue).getIdPartAsLong(); 219 } 220 221 public String getResourceType() { 222 if (isNotBlank(myResourceType)) { 223 return myResourceType; 224 } 225 if (isBlank(myChain)) { 226 return new IdDt(myValue).getResourceType(); 227 } 228 return null; 229 } 230 231 public Class<? extends IBaseResource> getResourceType(FhirContext theCtx) { 232 if (isBlank(getResourceType())) { 233 return null; 234 } 235 return theCtx.getResourceDefinition(getResourceType()).getImplementingClass(); 236 } 237 238 public String getValue() { 239 return myValue; 240 } 241 242 /** 243 * Note that the parameter to this method <b>must</b> be a resource reference, e.g 244 * <code>123</code> or <code>Patient/123</code> or <code>http://example.com/fhir/Patient/123</code> 245 * or something like this. This is not appropriate for cases where a chain is being used and 246 * the value is for a different type of parameter (e.g. a token). In that case, use one of the 247 * setter constructors. 248 */ 249 public ReferenceParam setValue(String theValue) { 250 IdDt id = new IdDt(theValue); 251 String qualifier = null; 252 if (id.hasResourceType()) { 253 qualifier = ":" + id.getResourceType(); 254 } 255 setValueAsQueryToken(null, null, qualifier, id.getIdPart()); 256 return this; 257 } 258 259 public boolean hasResourceType() { 260 return isNotBlank(myResourceType); 261 } 262 263 @Override 264 protected boolean isSupportsChain() { 265 return true; 266 } 267 268 /** 269 * Returns a new param containing the same value as this param, but with the type copnverted 270 * to {@link DateParam}. This is useful if you are using reference parameters and want to handle 271 * chained parameters of different types in a single method. 272 * <p> 273 * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a> 274 * in the HAPI FHIR documentation for an example of how to use this method. 275 * </p> 276 */ 277 public DateParam toDateParam(FhirContext theContext) { 278 DateParam retVal = new DateParam(); 279 retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken()); 280 return retVal; 281 } 282 283 /** 284 * Returns a new param containing the same value as this param, but with the type copnverted 285 * to {@link NumberParam}. This is useful if you are using reference parameters and want to handle 286 * chained parameters of different types in a single method. 287 * <p> 288 * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a> 289 * in the HAPI FHIR documentation for an example of how to use this method. 290 * </p> 291 */ 292 public NumberParam toNumberParam(FhirContext theContext) { 293 NumberParam retVal = new NumberParam(); 294 retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken()); 295 return retVal; 296 } 297 298 /** 299 * Returns a new param containing the same value as this param, but with the type copnverted 300 * to {@link QuantityParam}. This is useful if you are using reference parameters and want to handle 301 * chained parameters of different types in a single method. 302 * <p> 303 * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a> 304 * in the HAPI FHIR documentation for an example of how to use this method. 305 * </p> 306 */ 307 public QuantityParam toQuantityParam(FhirContext theContext) { 308 QuantityParam retVal = new QuantityParam(); 309 retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken()); 310 return retVal; 311 } 312 313 @Override 314 public String toString() { 315 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 316 if (isNotBlank(myChain)) { 317 b.append("chain", myChain); 318 } 319 b.append("value", getValue()); 320 return b.build(); 321 } 322 323 /** 324 * Returns a new param containing the same value as this param, but with the type copnverted 325 * to {@link StringParam}. This is useful if you are using reference parameters and want to handle 326 * chained parameters of different types in a single method. 327 * <p> 328 * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a> 329 * in the HAPI FHIR documentation for an example of how to use this method. 330 * </p> 331 */ 332 public StringParam toStringParam(FhirContext theContext) { 333 StringParam retVal = new StringParam(); 334 retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken()); 335 return retVal; 336 } 337 338 /** 339 * Returns a new param containing the same value as this param, but with the type copnverted 340 * to {@link TokenParam}. This is useful if you are using reference parameters and want to handle 341 * chained parameters of different types in a single method. 342 * <p> 343 * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a> 344 * in the HAPI FHIR documentation for an example of how to use this method. 345 * </p> 346 */ 347 public TokenParam toTokenParam(FhirContext theContext) { 348 TokenParam retVal = new TokenParam(); 349 retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken()); 350 return retVal; 351 } 352 353 public boolean isIdPartValidLong() { 354 return isValidLong(getIdPart()); 355 } 356 357 @Override 358 public boolean equals(Object theO) { 359 if (!(theO instanceof ReferenceParam that)) { 360 return false; 361 } 362 if (!super.equals(theO)) { 363 return false; 364 } 365 return Objects.equals(myChain, that.myChain) 366 && Objects.equals(myResourceType, that.myResourceType) 367 && Objects.equals(myBaseUrl, that.myBaseUrl) 368 && Objects.equals(myValue, that.myValue) 369 && Objects.equals(myIdPart, that.myIdPart) 370 && Objects.equals(myMdmExpand, that.myMdmExpand); 371 } 372 373 @Override 374 public int hashCode() { 375 return Objects.hash(super.hashCode(), myChain, myResourceType, myBaseUrl, myValue, myIdPart, myMdmExpand); 376 } 377}