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.api.annotation.Description;
029import ca.uhn.fhir.model.primitive.StringDt;
030import org.apache.commons.lang3.Validate;
031import org.hl7.fhir.instance.model.api.IBase;
032import org.hl7.fhir.instance.model.api.IBaseDatatype;
033import org.hl7.fhir.instance.model.api.IBaseParameters;
034import org.hl7.fhir.instance.model.api.IBaseReference;
035import org.hl7.fhir.instance.model.api.IBaseResource;
036import org.hl7.fhir.instance.model.api.IPrimitiveType;
037
038import javax.annotation.Nullable;
039import java.lang.annotation.Annotation;
040import java.lang.reflect.AnnotatedElement;
041import java.lang.reflect.Method;
042import java.math.BigDecimal;
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.Collection;
046import java.util.List;
047import java.util.Optional;
048import java.util.function.Function;
049import java.util.stream.Collectors;
050
051import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
052import static org.apache.commons.lang3.StringUtils.isBlank;
053
054/**
055 * Utilities for dealing with parameters resources in a version indepenedent way
056 */
057public class ParametersUtil {
058
059        public static List<String> getNamedParameterValuesAsString(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
060                Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null);
061                return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
062        }
063
064        public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
065                Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue();
066                return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
067        }
068
069        public static Optional<Integer> getNamedParameterValueAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
070                return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
071        }
072
073        public static List<IBase> getNamedParameters(FhirContext theCtx, IBaseResource theParameters, String theParameterName) {
074                Validate.notNull(theParameters, "theParameters must not be null");
075                RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
076                BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
077                List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
078
079                return parameterReps
080                        .stream()
081                        .filter(param -> {
082                                BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(param.getClass());
083                                BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
084                                List<IBase> nameValues = nameChild.getAccessor().getValues(param);
085                                Optional<? extends IPrimitiveType<?>> nameValue = nameValues
086                                        .stream()
087                                        .filter(t -> t instanceof IPrimitiveType<?>)
088                                        .map(t -> ((IPrimitiveType<?>) t))
089                                        .findFirst();
090                                return nameValue.isPresent() && theParameterName.equals(nameValue.get().getValueAsString());
091                        })
092                        .collect(Collectors.toList());
093
094        }
095
096        public static Optional<IBase> getParameterPart(FhirContext theCtx, IBase theParameter, String theParameterName) {
097                BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theParameter.getClass());
098                BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("part");
099                List<IBase> parts = valueChild.getAccessor().getValues(theParameter);
100
101                for (IBase nextPart : parts) {
102                        Optional<IPrimitiveType> name = theCtx.newTerser().getSingleValue(nextPart, "name", IPrimitiveType.class);
103                        if (name.isPresent() && theParameterName.equals(name.get().getValueAsString())) {
104                                return Optional.of(nextPart);
105                        }
106                }
107
108                return Optional.empty();
109        }
110
111        public static Optional<IBase> getParameterPartValue(FhirContext theCtx, IBase theParameter, String theParameterName) {
112                Optional<IBase> part = getParameterPart(theCtx, theParameter, theParameterName);
113                if (part.isPresent()) {
114                        return theCtx.newTerser().getSingleValue(part.get(), "value[x]", IBase.class);
115                } else {
116                        return Optional.empty();
117                }
118        }
119
120        public static String getParameterPartValueAsString(FhirContext theCtx, IBase theParameter, String theParameterName) {
121                return getParameterPartValue(theCtx, theParameter, theParameterName).map(t -> (IPrimitiveType<?>) t).map(t -> t.getValueAsString()).orElse(null);
122        }
123
124        private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
125                List<T> retVal = new ArrayList<>();
126
127                List<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
128                for (IBase nextParameter : namedParameters) {
129                        BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
130                        BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
131                        List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
132                        valueValues
133                                .stream()
134                                .filter(t -> t instanceof IPrimitiveType<?>)
135                                .map(t -> ((IPrimitiveType<?>) t))
136                                .map(theMapper)
137                                .filter(t -> t != null)
138                                .forEach(retVal::add);
139
140                }
141                return retVal;
142        }
143
144        private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
145                Validate.notNull(theValue, "theValue must not be null");
146
147                if (theValue instanceof IBaseResource) {
148                        IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
149                        paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue);
150                } else if (theValue instanceof IBaseDatatype) {
151                        IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName);
152                        paramChildElem.getChildByName("value[x]").getMutator().addValue(parameter, (IBaseDatatype) theValue);
153                } else if (theValue instanceof Collection) {
154                        Collection<?> collection = (Collection<?>) theValue;
155                        for (Object next : collection) {
156                                addClientParameter(theContext, next, theTargetResource, paramChild, paramChildElem, theName);
157                        }
158                } else {
159                        throw new IllegalArgumentException("Don't know how to handle value of type " + theValue.getClass() + " for parameter " + theName);
160                }
161        }
162
163        /**
164         * Add a parameter value to a Parameters resource
165         *
166         * @param theContext    The FhirContext
167         * @param theParameters The Parameters resource
168         * @param theName       The parametr name
169         * @param theValue      The parameter value (can be a {@link IBaseResource resource} or a {@link IBaseDatatype datatype})
170         */
171        public static void addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName, Object theValue) {
172                RuntimeResourceDefinition def = theContext.getResourceDefinition(theParameters);
173                BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
174                BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
175
176                addClientParameter(theContext, theValue, theParameters, paramChild, paramChildElem, theName);
177        }
178
179        /**
180         * Add a parameter value to a Parameters resource
181         *
182         * @param theContext           The FhirContext
183         * @param theParameters        The Parameters resource
184         * @param theName              The parameter name
185         * @param thePrimitiveDatatype The datatype, e.g. "string", or "uri"
186         * @param theValue             The value
187         */
188        public static void addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName, String thePrimitiveDatatype, String theValue) {
189                Validate.notBlank(thePrimitiveDatatype, "thePrimitiveDatatype must not be null or empty");
190
191                BaseRuntimeElementDefinition<?> datatypeDef = theContext.getElementDefinition(thePrimitiveDatatype);
192                IPrimitiveType<?> value = (IPrimitiveType<?>) datatypeDef.newInstance();
193                value.setValueAsString(theValue);
194
195                addParameterToParameters(theContext, theParameters, theName, value);
196        }
197
198        private static IBase createParameterRepetition(FhirContext theContext, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem, String theName) {
199                IBase parameter = paramChildElem.newInstance();
200                paramChild.getMutator().addValue(theTargetResource, parameter);
201                IPrimitiveType<?> value;
202                value = createString(theContext, theName);
203                paramChildElem.getChildByName("name").getMutator().addValue(parameter, value);
204                return parameter;
205        }
206
207        public static IPrimitiveType<?> createString(FhirContext theContext, String theValue) {
208                IPrimitiveType<?> value;
209                if (theContext.getVersion().getVersion().isRi()) {
210                        value = (IPrimitiveType<?>) theContext.getElementDefinition("string").newInstance(theValue);
211                } else {
212                        value = new StringDt(theValue);
213                }
214                return value;
215        }
216
217        public static IPrimitiveType<?> createUri(FhirContext theContext, String theValue) {
218                IPrimitiveType<?> value = (IPrimitiveType<?>) theContext.getElementDefinition("uri").newInstance(theValue);
219                return value;
220        }
221
222        public static IPrimitiveType<?> createCode(FhirContext theContext, String theValue) {
223                IPrimitiveType<?> value = (IPrimitiveType<?>) theContext.getElementDefinition("code").newInstance(theValue);
224                return value;
225        }
226
227        public static IBaseParameters newInstance(FhirContext theContext) {
228                Validate.notNull(theContext, "theContext must not be null");
229                return (IBaseParameters) theContext.getResourceDefinition("Parameters").newInstance();
230        }
231
232        @SuppressWarnings("unchecked")
233        public static void addParameterToParametersBoolean(FhirContext theCtx, IBaseParameters theParameters, String theName, boolean theValue) {
234                addParameterToParameters(theCtx, theParameters, theName, theCtx.getPrimitiveBoolean(theValue));
235        }
236
237        @SuppressWarnings("unchecked")
238        public static void addParameterToParametersCode(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
239                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("code").newInstance();
240                value.setValue(theValue);
241                addParameterToParameters(theCtx, theParameters, theName, value);
242        }
243
244        @SuppressWarnings("unchecked")
245        public static void addParameterToParametersInteger(FhirContext theCtx, IBaseParameters theParameters, String theName, int theValue) {
246                IPrimitiveType<Integer> count = (IPrimitiveType<Integer>) theCtx.getElementDefinition("integer").newInstance();
247                count.setValue(theValue);
248                addParameterToParameters(theCtx, theParameters, theName, count);
249        }
250
251        public static void addParameterToParametersLong(FhirContext theCtx, IBaseParameters theParameters, String theName, long theValue) {
252                addParameterToParametersDecimal(theCtx, theParameters, theName, BigDecimal.valueOf(theValue));
253        }
254
255        public static void addParameterToParametersDecimal(FhirContext theCtx, IBaseParameters theParameters, String theName, BigDecimal theValue) {
256                IPrimitiveType<BigDecimal> count = (IPrimitiveType<BigDecimal>) theCtx.getElementDefinition("decimal").newInstance();
257                count.setValue(theValue);
258                addParameterToParameters(theCtx, theParameters, theName, count);
259        }
260
261        public static void addParameterToParametersReference(FhirContext theCtx, IBaseParameters theParameters, String theName, String theReference) {
262                IBaseReference target = (IBaseReference) theCtx.getElementDefinition("reference").newInstance();
263                target.setReference(theReference);
264                addParameterToParameters(theCtx, theParameters, theName, target);
265        }
266
267        @SuppressWarnings("unchecked")
268        public static void addParameterToParametersString(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
269                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("string").newInstance();
270                value.setValue(theValue);
271                addParameterToParameters(theCtx, theParameters, theName, value);
272        }
273
274        @SuppressWarnings("unchecked")
275        public static void addParameterToParametersUri(FhirContext theCtx, IBaseParameters theParameters, String theName, String theValue) {
276                IPrimitiveType<String> value = (IPrimitiveType<String>) theCtx.getElementDefinition("uri").newInstance();
277                value.setValue(theValue);
278                addParameterToParameters(theCtx, theParameters, theName, value);
279
280        }
281
282        /**
283         * Add a parameter with no value (typically because we'll be adding sub-parameters)
284         */
285        public static IBase addParameterToParameters(FhirContext theContext, IBaseParameters theParameters, String theName) {
286                RuntimeResourceDefinition def = theContext.getResourceDefinition(theParameters);
287                BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
288                BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
289
290                return createParameterRepetition(theContext, theParameters, paramChild, paramChildElem, theName);
291        }
292
293        public static void addPartCode(FhirContext theContext, IBase theParameter, String theName, String theCode) {
294                IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("code").newInstance();
295                value.setValue(theCode);
296
297                addPart(theContext, theParameter, theName, value);
298        }
299
300        public static void addPartInteger(FhirContext theContext, IBase theParameter, String theName, Integer theInteger) {
301                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theContext.getElementDefinition("integer").newInstance();
302                value.setValue(theInteger);
303
304                addPart(theContext, theParameter, theName, value);
305        }
306
307        public static void addPartString(FhirContext theContext, IBase theParameter, String theName, String theValue) {
308                IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
309                value.setValue(theValue);
310
311                addPart(theContext, theParameter, theName, value);
312        }
313
314        public static void addPartBoolean(FhirContext theContext, IBase theParameter, String theName, Boolean theValue) {
315                addPart(theContext, theParameter, theName, theContext.getPrimitiveBoolean(theValue));
316        }
317
318        public static void addPartDecimal(FhirContext theContext, IBase theParameter, String theName, Double theValue) {
319                IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theContext.getElementDefinition("decimal").newInstance();
320                value.setValue(theValue == null ? null : new BigDecimal(theValue));
321
322                addPart(theContext, theParameter, theName, value);
323        }
324
325        public static void addPartCoding(FhirContext theContext, IBase theParameter, String theName, String theSystem, String theCode, String theDisplay) {
326                IBase coding = theContext.getElementDefinition("coding").newInstance();
327
328                BaseRuntimeElementCompositeDefinition<?> codingDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(coding.getClass());
329                codingDef.getChildByName("system").getMutator().addValue(coding, createUri(theContext, theSystem));
330                codingDef.getChildByName("code").getMutator().addValue(coding, createCode(theContext, theCode));
331                codingDef.getChildByName("display").getMutator().addValue(coding, createString(theContext, theDisplay));
332
333                addPart(theContext, theParameter, theName, coding);
334        }
335
336        public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
337                BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
338                BaseRuntimeChildDefinition partChild = def.getChildByName("part");
339
340                BaseRuntimeElementCompositeDefinition<?> partChildElem = (BaseRuntimeElementCompositeDefinition<?>) partChild.getChildByName("part");
341                IBase part = partChildElem.newInstance();
342                partChild.getMutator().addValue(theParameter, part);
343
344                IPrimitiveType<String> name = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
345                name.setValue(theName);
346                partChildElem.getChildByName("name").getMutator().addValue(part, name);
347
348                if (theValue instanceof IBaseResource) {
349                        partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
350                } else {
351                        partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
352                }
353        }
354
355        public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) {
356                BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
357                BaseRuntimeChildDefinition partChild = def.getChildByName("part");
358
359                BaseRuntimeElementCompositeDefinition<?> partChildElem = (BaseRuntimeElementCompositeDefinition<?>) partChild.getChildByName("part");
360                IBase part = partChildElem.newInstance();
361                partChild.getMutator().addValue(theParameter, part);
362
363                IPrimitiveType<String> name = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
364                name.setValue(theName);
365                partChildElem.getChildByName("name").getMutator().addValue(part, name);
366
367                partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
368        }
369
370        public static List<String> getNamedParameterPartAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
371                return extractNamedParameterPartsAsString(theCtx, theParameters, thePartName, theParameterName);
372        }
373
374        // TODO KHS need to consolidate duplicated functionality that came in from different branches
375        private static List<String> extractNamedParameterPartsAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
376                List<IBase> parameterReps = getParameterReps(theCtx, theParameters);
377
378                List<String> retVal = new ArrayList<>();
379
380                for (IBase nextParameter : parameterReps) {
381                        BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
382                        Optional<? extends IPrimitiveType<?>> nameValue = getNameValue(nextParameter, nextParameterDef);
383                        if (!nameValue.isPresent() || !thePartName.equals(nameValue.get().getValueAsString())) {
384                                continue;
385                        }
386
387                        BaseRuntimeChildDefinition partChild = nextParameterDef.getChildByName("part");
388                        List<IBase> partValues = partChild.getAccessor().getValues(nextParameter);
389                        for (IBase partValue : partValues) {
390                                BaseRuntimeElementCompositeDefinition<?> partParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(partValue.getClass());
391                                Optional<? extends IPrimitiveType<?>> partNameValue = getNameValue(partValue, partParameterDef);
392                                if (!partNameValue.isPresent() || !theParameterName.equals(partNameValue.get().getValueAsString())) {
393                                        continue;
394                                }
395                                BaseRuntimeChildDefinition valueChild = partParameterDef.getChildByName("value[x]");
396                                List<IBase> valueValues = valueChild.getAccessor().getValues(partValue);
397                                valueValues
398                                        .stream()
399                                        .filter(t -> t instanceof IPrimitiveType<?>)
400                                        .map(t -> ((IPrimitiveType<String>) t))
401                                        .map(t -> defaultIfBlank(t.getValueAsString(), null))
402                                        .filter(t -> t != null)
403                                        .forEach(retVal::add);
404
405                        }
406                }
407                return retVal;
408        }
409
410        private static List<IBase> getParameterReps(FhirContext theCtx, IBaseParameters theParameters) {
411                Validate.notNull(theParameters, "theParameters must not be null");
412                RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
413                BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
414                return parameterChild.getAccessor().getValues(theParameters);
415        }
416
417        private static Optional<? extends IPrimitiveType<?>> getNameValue(IBase nextParameter, BaseRuntimeElementCompositeDefinition<?> theNextParameterDef) {
418                BaseRuntimeChildDefinition nameChild = theNextParameterDef.getChildByName("name");
419                List<IBase> nameValues = nameChild.getAccessor().getValues(nextParameter);
420                return nameValues
421                        .stream()
422                        .filter(t -> t instanceof IPrimitiveType<?>)
423                        .map(t -> ((IPrimitiveType<?>) t))
424                        .findFirst();
425        }
426
427        @Nullable
428        public static String extractDescription(AnnotatedElement theType) {
429                Description description = theType.getAnnotation(Description.class);
430                if (description != null) {
431                        return extractDescription(description);
432                } else {
433                        return null;
434                }
435        }
436
437        @Nullable
438        public static String extractDescription(Description desc) {
439                String description = desc.value();
440                if (isBlank(description)) {
441                        description = desc.formalDefinition();
442                }
443                if (isBlank(description)) {
444                        description = desc.shortDefinition();
445                }
446                return defaultIfBlank(description, null);
447        }
448
449        @Nullable
450        public static String extractShortDefinition(AnnotatedElement theType) {
451                Description description = theType.getAnnotation(Description.class);
452                if (description != null) {
453                        return defaultIfBlank(description.shortDefinition(), null);
454                } else {
455                        return null;
456                }
457        }
458
459        public static String extractDescription(Annotation[] theParameterAnnotations) {
460                for (Annotation next : theParameterAnnotations) {
461                        if (next instanceof Description) {
462                                return extractDescription((Description)next);
463                        }
464                }
465                return null;
466        }
467
468        public static List<String> extractExamples(Annotation[] theParameterAnnotations) {
469                ArrayList<String> retVal = null;
470                for (Annotation next : theParameterAnnotations) {
471                        if (next instanceof Description) {
472                                String[] examples = ((Description) next).example();
473                                if (examples.length > 0) {
474                                        if (retVal == null) {
475                                                retVal = new ArrayList<>();
476                                        }
477                                        retVal.addAll(Arrays.asList(examples));
478                                }
479                        }
480                }
481                return retVal;
482        }
483}