001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 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}