001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
026import ca.uhn.fhir.context.FhirContext;
027import ca.uhn.fhir.context.RuntimeResourceDefinition;
028import ca.uhn.fhir.model.primitive.StringDt;
029import org.apache.commons.lang3.Validate;
030import org.hl7.fhir.instance.model.api.IBase;
031import org.hl7.fhir.instance.model.api.IBaseDatatype;
032import org.hl7.fhir.instance.model.api.IBaseParameters;
033import org.hl7.fhir.instance.model.api.IBaseReference;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IPrimitiveType;
036
037import java.math.BigDecimal;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.List;
041import java.util.Optional;
042import java.util.function.Function;
043import java.util.stream.Collectors;
044
045import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
046
047/**
048 * Utilities for dealing with parameters resources in a version indepenedent way
049 */
050public class ParametersUtil {
051
052        public static List<String> getNamedParameterValuesAsString(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
053                Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null);
054                return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
055        }
056
057        public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
058                Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue();
059                return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
060        }
061
062        public static Optional<Integer> getNamedParameterValueAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
063                return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
064        }
065
066        public static List<IBase> getNamedParameters(FhirContext theCtx, IBaseResource theParameters, String theParameterName) {
067                Validate.notNull(theParameters, "theParameters must not be null");
068                RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
069                BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
070                List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
071
072                return parameterReps
073                        .stream()
074                        .filter(param -> {
075                                BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(param.getClass());
076                                BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
077                                List<IBase> nameValues = nameChild.getAccessor().getValues(param);
078                                Optional<? extends IPrimitiveType<?>> nameValue = nameValues
079                                        .stream()
080                                        .filter(t -> t instanceof IPrimitiveType<?>)
081                                        .map(t -> ((IPrimitiveType<?>) t))
082                                        .findFirst();
083                                return nameValue.isPresent() && theParameterName.equals(nameValue.get().getValueAsString());
084                        })
085                        .collect(Collectors.toList());
086
087        }
088
089        public static Optional<IBase> getParameterPart(FhirContext theCtx, IBase theParameter, String theParameterName) {
090                BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theParameter.getClass());
091                BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("part");
092                List<IBase> parts = valueChild.getAccessor().getValues(theParameter);
093
094                for (IBase nextPart : parts) {
095                        Optional<IPrimitiveType> name = theCtx.newTerser().getSingleValue(nextPart, "name", IPrimitiveType.class);
096                        if (name.isPresent() && theParameterName.equals(name.get().getValueAsString())) {
097                                return Optional.of(nextPart);
098                        }
099                }
100
101                return Optional.empty();
102        }
103
104        public static Optional<IBase> getParameterPartValue(FhirContext theCtx, IBase theParameter, String theParameterName) {
105                Optional<IBase> part = getParameterPart(theCtx, theParameter, theParameterName);
106                if (part.isPresent()) {
107                        return theCtx.newTerser().getSingleValue(part.get(), "value[x]", IBase.class);
108                } else {
109                        return Optional.empty();
110                }
111        }
112
113        public static String getParameterPartValueAsString(FhirContext theCtx, IBase theParameter, String theParameterName) {
114                return getParameterPartValue(theCtx, theParameter, theParameterName).map(t -> (IPrimitiveType<?>) t).map(t -> t.getValueAsString()).orElse(null);
115        }
116
117        private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
118                List<T> retVal = new ArrayList<>();
119
120                List<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
121                for (IBase nextParameter : namedParameters) {
122                        BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
123                        BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
124                        List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
125                        valueValues
126                                .stream()
127                                .filter(t -> t instanceof IPrimitiveType<?>)
128                                .map(t -> ((IPrimitiveType<?>) t))
129                                .map(theMapper)
130                                .filter(t -> t != null)
131                                .forEach(retVal::add);
132
133                }
134                return retVal;
135        }
136
137        private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
138                Validate.notNull(theValue, "theValue must not be null");
139
140                if (theValue instanceof IBaseResource) {
141                        IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
142                        paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue);
143                } else if (theValue instanceof IBaseDatatype) {
144                        IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
145                        paramChildElem.getChildByName("value[x]").getMutator().addValue(parameter, (IBaseDatatype) theValue);
146                } else if (theValue instanceof Collection) {
147                        Collection<?> collection = (Collection<?>) theValue;
148                        for (Object next : collection) {
149                                addClientParameter(theContext, next, theTargetResource, paramChild, paramChildElem, theName);
150                        }
151                } else {
152                        throw new IllegalArgumentException("Don't know how to handle value of type " + theValue.getClass() + " for parameter " + theName);
153                }
154        }
155
156        /**
157         * Add a parameter value to a Parameters resource
158         *
159         * @param theContext    The FhirContext
160         * @param theParameters The Parameters resource
161         * @param theName       The parametr name
162         * @param theValue      The parameter value (can be a {@link IBaseResource resource} or a {@link IBaseDatatype datatype})
163         */
164        public static void addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName, Object theValue) {
165                RuntimeResourceDefinition def = theContext.getResourceDefinition(theParameters);
166                BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
167                BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
168
169                addClientParameter(theContext, theValue, theParameters, paramChild, paramChildElem, theName);
170        }
171
172        /**
173         * Add a parameter value to a Parameters resource
174         *
175         * @param theContext           The FhirContext
176         * @param theParameters        The Parameters resource
177         * @param theName              The parameter name
178         * @param thePrimitiveDatatype The datatype, e.g. "string", or "uri"
179         * @param theValue             The value
180         */
181        public static void addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName, String thePrimitiveDatatype, String theValue) {
182                Validate.notBlank(thePrimitiveDatatype, "thePrimitiveDatatype must not be null or empty");
183
184                BaseRuntimeElementDefinition<?> datatypeDef = theContext.getElementDefinition(thePrimitiveDatatype);
185                IPrimitiveType<?> value = (IPrimitiveType<?>) datatypeDef.newInstance();
186                value.setValueAsString(theValue);
187
188                addParameterToParameters(theContext, theParameters, theName, value);
189        }
190
191        private static IBase createParameterRepetition(FhirContext theContext, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
192                IBase parameter = paramChildElem.newInstance();
193                paramChild.getMutator().addValue(theTargetResource, parameter);
194                IPrimitiveType<?> value;
195                value = createString(theContext, theName);
196                paramChildElem.getChildByName("name").getMutator().addValue(parameter, value);
197                return parameter;
198        }
199
200        public static IPrimitiveType<?> createString(FhirContext theContext, String theValue) {
201                IPrimitiveType<?> value;
202                if (theContext.getVersion().getVersion().isRi()) {
203                        value = (IPrimitiveType<?>) theContext.getElementDefinition("string").newInstance(theValue);
204                } else {
205                        value = new StringDt(theValue);
206                }
207                return value;
208        }
209
210        public static IPrimitiveType<?> createUri(FhirContext theContext, String theValue) {
211                IPrimitiveType<?> value = (IPrimitiveType<?>) theContext.getElementDefinition("uri").newInstance(theValue);
212                return value;
213        }
214
215        public static IPrimitiveType<?> createCode(FhirContext theContext, String theValue) {
216                IPrimitiveType<?> value = (IPrimitiveType<?>) theContext.getElementDefinition("code").newInstance(theValue);
217                return value;
218        }
219
220        public static IBaseParameters newInstance(FhirContext theContext) {
221                Validate.notNull(theContext, "theContext must not be null");
222                return (IBaseParameters) theContext.getResourceDefinition("Parameters").newInstance();
223        }
224
225        @SuppressWarnings("unchecked")
226        public static void addParameterToParametersBoolean(FhirContext theCtx, IBaseParameters theParameters, String theName, boolean theValue) {
227                addParameterToParameters(theCtx, theParameters, theName, theCtx.getPrimitiveBoolean(theValue));
228        }
229
230        @SuppressWarnings("unchecked")
231        public static void addParameterToParametersCode(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
232                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("code").newInstance();
233                value.setValue(theValue);
234                addParameterToParameters(theCtx, theParameters, theName, value);
235        }
236
237        @SuppressWarnings("unchecked")
238        public static void addParameterToParametersInteger(FhirContext theCtx, IBaseParameters theParameters, String theName, int theValue) {
239                IPrimitiveType<Integer> count = (IPrimitiveType<Integer>) theCtx.getElementDefinition("integer").newInstance();
240                count.setValue(theValue);
241                addParameterToParameters(theCtx, theParameters, theName, count);
242        }
243
244        public static void addParameterToParametersLong(FhirContext theCtx, IBaseParameters theParameters, String theName, long theValue) {
245                addParameterToParametersDecimal(theCtx, theParameters, theName, BigDecimal.valueOf(theValue));
246        }
247
248        public static void addParameterToParametersDecimal(FhirContext theCtx, IBaseParameters theParameters, String theName, BigDecimal theValue) {
249                IPrimitiveType<BigDecimal> count = (IPrimitiveType<BigDecimal>) theCtx.getElementDefinition("decimal").newInstance();
250                count.setValue(theValue);
251                addParameterToParameters(theCtx, theParameters, theName, count);
252        }
253
254        public static void addParameterToParametersReference(FhirContext theCtx, IBaseParameters theParameters, String theName, String theReference) {
255                IBaseReference target = (IBaseReference) theCtx.getElementDefinition("reference").newInstance();
256                target.setReference(theReference);
257                addParameterToParameters(theCtx, theParameters, theName, target);
258        }
259
260        @SuppressWarnings("unchecked")
261        public static void addParameterToParametersString(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
262                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("string").newInstance();
263                value.setValue(theValue);
264                addParameterToParameters(theCtx, theParameters, theName, value);
265        }
266
267        @SuppressWarnings("unchecked")
268        public static void addParameterToParametersUri(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
269                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("uri").newInstance();
270                value.setValue(theValue);
271                addParameterToParameters(theCtx, theParameters, theName, value);
272
273        }
274
275        /**
276         * Add a parameter with no value (typically because we'll be adding sub-parameters)
277         */
278        public static IBase addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName) {
279                RuntimeResourceDefinition def = theContext.getResourceDefinition(theParameters);
280                BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
281                BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
282
283                return createParameterRepetition(theContext, theParameters, paramChild, paramChildElem, theName);
284        }
285
286        public static void addPartCode(FhirContext theContext, IBase theParameter, String theName, String theCode) {
287                IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("code").newInstance();
288                value.setValue(theCode);
289
290                addPart(theContext, theParameter, theName, value);
291        }
292
293        public static void addPartInteger(FhirContext theContext, IBase theParameter, String theName, Integer theInteger) {
294                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theContext.getElementDefinition("integer").newInstance();
295                value.setValue(theInteger);
296
297                addPart(theContext, theParameter, theName, value);
298        }
299
300        public static void addPartString(FhirContext theContext, IBase theParameter, String theName, String theValue) {
301                IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
302                value.setValue(theValue);
303
304                addPart(theContext, theParameter, theName, value);
305        }
306
307        public static void addPartBoolean(FhirContext theContext, IBase theParameter, String theName, Boolean theValue) {
308                addPart(theContext, theParameter, theName, theContext.getPrimitiveBoolean(theValue));
309        }
310
311        public static void addPartDecimal(FhirContext theContext, IBase theParameter, String theName, Double theValue) {
312                IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theContext.getElementDefinition("decimal").newInstance();
313                value.setValue(theValue == null ? null : new BigDecimal(theValue));
314
315                addPart(theContext, theParameter, theName, value);
316        }
317
318        public static void addPartCoding(FhirContext theContext, IBase theParameter, String theName, String theSystem, String theCode, String theDisplay) {
319                IBase coding = theContext.getElementDefinition("coding").newInstance();
320
321                BaseRuntimeElementCompositeDefinition<?> codingDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(coding.getClass());
322                codingDef.getChildByName("system").getMutator().addValue(coding, createUri(theContext, theSystem));
323                codingDef.getChildByName("code").getMutator().addValue(coding, createCode(theContext, theCode));
324                codingDef.getChildByName("display").getMutator().addValue(coding, createString(theContext, theDisplay));
325
326                addPart(theContext, theParameter, theName, coding);
327        }
328
329        public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
330                BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
331                BaseRuntimeChildDefinition partChild = def.getChildByName("part");
332
333                BaseRuntimeElementCompositeDefinition<?> partChildElem = (BaseRuntimeElementCompositeDefinition<?>) partChild.getChildByName("part");
334                IBase part = partChildElem.newInstance();
335                partChild.getMutator().addValue(theParameter, part);
336
337                IPrimitiveType<String> name = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
338                name.setValue(theName);
339                partChildElem.getChildByName("name").getMutator().addValue(part, name);
340
341                if (theValue instanceof IBaseResource) {
342                        partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
343                } else {
344                        partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
345                }
346
347        }
348
349        public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) {
350                BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
351                BaseRuntimeChildDefinition partChild = def.getChildByName("part");
352
353                BaseRuntimeElementCompositeDefinition<?> partChildElem = (BaseRuntimeElementCompositeDefinition<?>) partChild.getChildByName("part");
354                IBase part = partChildElem.newInstance();
355                partChild.getMutator().addValue(theParameter, part);
356
357                IPrimitiveType<String> name = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
358                name.setValue(theName);
359                partChildElem.getChildByName("name").getMutator().addValue(part, name);
360
361                partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
362        }
363
364        public static List<String> getNamedParameterPartAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
365                return extractNamedParameterPartsAsString(theCtx, theParameters, thePartName, theParameterName);
366        }
367
368        // TODO KHS need to consolidate duplicated functionality that came in from different branches
369        private static List<String> extractNamedParameterPartsAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
370                List<IBase> parameterReps = getParameterReps(theCtx, theParameters);
371
372                List<String> retVal = new ArrayList<>();
373
374                for (IBase nextParameter : parameterReps) {
375                        BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
376                        Optional<? extends IPrimitiveType<?>> nameValue = getNameValue(nextParameter, nextParameterDef);
377                        if (!nameValue.isPresent() || !thePartName.equals(nameValue.get().getValueAsString())) {
378                                continue;
379                        }
380
381                        BaseRuntimeChildDefinition partChild = nextParameterDef.getChildByName("part");
382                        List<IBase> partValues = partChild.getAccessor().getValues(nextParameter);
383                        for (IBase partValue : partValues) {
384                                BaseRuntimeElementCompositeDefinition<?> partParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(partValue.getClass());
385                                Optional<? extends IPrimitiveType<?>> partNameValue = getNameValue(partValue, partParameterDef);
386                                if (!partNameValue.isPresent() || !theParameterName.equals(partNameValue.get().getValueAsString())) {
387                                        continue;
388                                }
389                                BaseRuntimeChildDefinition valueChild = partParameterDef.getChildByName("value[x]");
390                                List<IBase> valueValues = valueChild.getAccessor().getValues(partValue);
391                                valueValues
392                                        .stream()
393                                        .filter(t -> t instanceof IPrimitiveType<?>)
394                                        .map(t -> ((IPrimitiveType<String>) t))
395                                        .map(t -> defaultIfBlank(t.getValueAsString(), null))
396                                        .filter(t -> t != null)
397                                        .forEach(retVal::add);
398
399                        }
400                }
401                return retVal;
402        }
403
404        private static List<IBase> getParameterReps(FhirContext theCtx, IBaseParameters theParameters) {
405                Validate.notNull(theParameters, "theParameters must not be null");
406                RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
407                BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
408                return parameterChild.getAccessor().getValues(theParameters);
409        }
410
411        private static Optional<? extends IPrimitiveType<?>> getNameValue(IBase nextParameter, BaseRuntimeElementCompositeDefinition<?> theNextParameterDef) {
412                BaseRuntimeChildDefinition nameChild = theNextParameterDef.getChildByName("name");
413                List<IBase> nameValues = nameChild.getAccessor().getValues(nextParameter);
414                return nameValues
415                        .stream()
416                        .filter(t -> t instanceof IPrimitiveType<?>)
417                        .map(t -> ((IPrimitiveType<?>) t))
418                        .findFirst();
419        }
420
421}