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.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
095        private String defaultGetQueryParameterQualifier() {
096                StringBuilder b = new StringBuilder();
097                if (isNotBlank(myChain)) {
098                        if (isNotBlank(getResourceType())) {
099                                b.append(':');
100                                b.append(getResourceType());
101                        }
102                        b.append('.');
103                        b.append(myChain);
104                }
105                if (b.length() != 0) {
106                        return b.toString();
107                }
108                return null;
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
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        public String getChain() {
189                return myChain;
190        }
191
192        public ReferenceParam setChain(String theChain) {
193                myChain = theChain;
194                return this;
195        }
196
197        @CoverageIgnore
198        public String getIdPart() {
199                return myIdPart;
200        }
201
202        @CoverageIgnore
203        public BigDecimal getIdPartAsBigDecimal() {
204                return new IdDt(myValue).getIdPartAsBigDecimal();
205        }
206
207        @CoverageIgnore
208        public Long getIdPartAsLong() {
209                return new IdDt(myValue).getIdPartAsLong();
210        }
211
212        public String getResourceType() {
213                if (isNotBlank(myResourceType)) {
214                        return myResourceType;
215                }
216                if (isBlank(myChain)) {
217                        return new IdDt(myValue).getResourceType();
218                }
219                return null;
220        }
221
222        public Class<? extends IBaseResource> getResourceType(FhirContext theCtx) {
223                if (isBlank(getResourceType())) {
224                        return null;
225                }
226                return theCtx.getResourceDefinition(getResourceType()).getImplementingClass();
227        }
228
229        public String getValue() {
230                return myValue;
231        }
232
233        /**
234         * Note that the parameter to this method <b>must</b> be a resource reference, e.g
235         * <code>123</code> or <code>Patient/123</code> or <code>http://example.com/fhir/Patient/123</code>
236         * or something like this. This is not appropriate for cases where a chain is being used and
237         * the value is for a different type of parameter (e.g. a token). In that case, use one of the
238         * setter constructors.
239         */
240        public ReferenceParam setValue(String theValue) {
241                IdDt id = new IdDt(theValue);
242                String qualifier = null;
243                if (id.hasResourceType()) {
244                        qualifier = ":" + id.getResourceType();
245                }
246                setValueAsQueryToken(null, null, qualifier, id.getIdPart());
247                return this;
248        }
249
250        public boolean hasResourceType() {
251                return isNotBlank(myResourceType);
252        }
253
254        @Override
255        protected boolean isSupportsChain() {
256                return true;
257        }
258
259        /**
260         * Returns a new param containing the same value as this param, but with the type copnverted
261         * to {@link DateParam}. This is useful if you are using reference parameters and want to handle
262         * chained parameters of different types in a single method.
263         * <p>
264         * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a>
265         * in the HAPI FHIR documentation for an example of how to use this method.
266         * </p>
267         */
268        public DateParam toDateParam(FhirContext theContext) {
269                DateParam retVal = new DateParam();
270                retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken(theContext));
271                return retVal;
272        }
273
274        /**
275         * Returns a new param containing the same value as this param, but with the type copnverted
276         * to {@link NumberParam}. This is useful if you are using reference parameters and want to handle
277         * chained parameters of different types in a single method.
278         * <p>
279         * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a>
280         * in the HAPI FHIR documentation for an example of how to use this method.
281         * </p>
282         */
283        public NumberParam toNumberParam(FhirContext theContext) {
284                NumberParam retVal = new NumberParam();
285                retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken(theContext));
286                return retVal;
287        }
288
289        /**
290         * Returns a new param containing the same value as this param, but with the type copnverted
291         * to {@link QuantityParam}. This is useful if you are using reference parameters and want to handle
292         * chained parameters of different types in a single method.
293         * <p>
294         * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a>
295         * in the HAPI FHIR documentation for an example of how to use this method.
296         * </p>
297         */
298        public QuantityParam toQuantityParam(FhirContext theContext) {
299                QuantityParam retVal = new QuantityParam();
300                retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken(theContext));
301                return retVal;
302        }
303
304        @Override
305        public String toString() {
306                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
307                if (isNotBlank(myChain)) {
308                        b.append("chain", myChain);
309                }
310                b.append("value", getValue());
311                return b.build();
312        }
313
314        /**
315         * Returns a new param containing the same value as this param, but with the type copnverted
316         * to {@link StringParam}. This is useful if you are using reference parameters and want to handle
317         * chained parameters of different types in a single method.
318         * <p>
319         * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a>
320         * in the HAPI FHIR documentation for an example of how to use this method.
321         * </p>
322         */
323        public StringParam toStringParam(FhirContext theContext) {
324                StringParam retVal = new StringParam();
325                retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken(theContext));
326                return retVal;
327        }
328
329        /**
330         * Returns a new param containing the same value as this param, but with the type copnverted
331         * to {@link TokenParam}. This is useful if you are using reference parameters and want to handle
332         * chained parameters of different types in a single method.
333         * <p>
334         * See <a href="https://hapifhir.io/hapi-fhir/docs/server_plain/rest_operations_search.html#chained-resource-references">Dynamic Chains</a>
335         * in the HAPI FHIR documentation for an example of how to use this method.
336         * </p>
337         */
338        public TokenParam toTokenParam(FhirContext theContext) {
339                TokenParam retVal = new TokenParam();
340                retVal.setValueAsQueryToken(theContext, null, null, getValueAsQueryToken(theContext));
341                return retVal;
342        }
343
344        public boolean isIdPartValidLong() {
345                return isValidLong(getIdPart());
346        }
347}