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}