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