001package ca.uhn.fhir.test.utilities; 002 003/*- 004 * #%L 005 * HAPI FHIR Test Utilities 006 * %% 007 * Copyright (C) 2014 - 2023 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.FhirContext; 026import ca.uhn.fhir.context.RuntimeResourceDefinition; 027import ca.uhn.fhir.util.FhirTerser; 028import ca.uhn.fhir.util.MetaUtil; 029import org.apache.commons.lang3.Validate; 030import org.hl7.fhir.instance.model.api.IBase; 031import org.hl7.fhir.instance.model.api.IBaseReference; 032import org.hl7.fhir.instance.model.api.IBaseResource; 033import org.hl7.fhir.instance.model.api.ICompositeType; 034import org.hl7.fhir.instance.model.api.IIdType; 035import org.hl7.fhir.instance.model.api.IPrimitiveType; 036import org.hl7.fhir.r4.model.IdType; 037import org.hl7.fhir.r4.model.InstantType; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import javax.annotation.Nonnull; 042import javax.annotation.Nullable; 043import java.util.Collection; 044import java.util.Date; 045import java.util.function.Consumer; 046 047import static org.apache.commons.lang3.StringUtils.isNotBlank; 048import static org.hamcrest.MatcherAssert.assertThat; 049import static org.hamcrest.Matchers.matchesPattern; 050 051/** 052 * This is an experiment to see if we can make test data creation for storage unit tests a bit more readable. 053 */ 054@SuppressWarnings({"unchecked", "ConstantConditions"}) 055public interface ITestDataBuilder { 056 Logger ourLog = LoggerFactory.getLogger(ITestDataBuilder.class); 057 058 /** 059 * Set Patient.active = true 060 */ 061 default Consumer<IBaseResource> withActiveTrue() { 062 return t -> __setPrimitiveChild(getFhirContext(), t, "active", "boolean", "true"); 063 } 064 065 /** 066 * Set Patient.active = false 067 */ 068 default Consumer<IBaseResource> withActiveFalse() { 069 return t -> __setPrimitiveChild(getFhirContext(), t, "active", "boolean", "false"); 070 } 071 072 default Consumer<IBaseResource> withFamily(String theFamily) { 073 return t -> { 074 IPrimitiveType<?> family = (IPrimitiveType<?>) getFhirContext().getElementDefinition("string").newInstance(); 075 family.setValueAsString(theFamily); 076 077 BaseRuntimeElementCompositeDefinition<?> humanNameDef = (BaseRuntimeElementCompositeDefinition<?>) getFhirContext().getElementDefinition("HumanName"); 078 ICompositeType humanName = (ICompositeType) humanNameDef.newInstance(); 079 humanNameDef.getChildByName("family").getMutator().addValue(humanName, family); 080 081 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t.getClass()); 082 resourceDef.getChildByName("name").getMutator().addValue(t, humanName); 083 }; 084 } 085 086 087 /** Patient.name.given */ 088 default <T extends IBaseResource> Consumer<T> withGiven(String theName) { 089 return withPrimitiveAttribute("name.given", theName); 090 } 091 092 093 /** 094 * Set Patient.birthdate 095 */ 096 default Consumer<IBaseResource> withBirthdate(String theBirthdate) { 097 return t -> __setPrimitiveChild(getFhirContext(), t, "birthDate", "dateTime", theBirthdate); 098 } 099 100 /** 101 * Set Observation.status 102 */ 103 default Consumer<IBaseResource> withStatus(String theStatus) { 104 return t -> __setPrimitiveChild(getFhirContext(), t, "status", "code", theStatus); 105 } 106 107 /** 108 * Set Observation.effectiveDate 109 */ 110 default Consumer<IBaseResource> withEffectiveDate(String theDate) { 111 return t -> __setPrimitiveChild(getFhirContext(), t, "effectiveDateTime", "dateTime", theDate); 112 } 113 114 /** 115 * Set Observation.effectiveDate 116 */ 117 default Consumer<IBaseResource> withDateTimeAt(String thePath, String theDate) { 118 return t -> __setPrimitiveChild(getFhirContext(), t, thePath, "dateTime", theDate); 119 } 120 121 /** 122 * Set [Resource].identifier.system and [Resource].identifier.value 123 */ 124 default Consumer<IBaseResource> withIdentifier(String theSystem, String theValue) { 125 return t -> { 126 IPrimitiveType<?> system = (IPrimitiveType<?>) getFhirContext().getElementDefinition("uri").newInstance(); 127 system.setValueAsString(theSystem); 128 129 IPrimitiveType<?> value = (IPrimitiveType<?>) getFhirContext().getElementDefinition("string").newInstance(); 130 value.setValueAsString(theValue); 131 132 BaseRuntimeElementCompositeDefinition<?> identifierDef = (BaseRuntimeElementCompositeDefinition<?>) getFhirContext().getElementDefinition("Identifier"); 133 ICompositeType identifier = (ICompositeType) identifierDef.newInstance(); 134 identifierDef.getChildByName("system").getMutator().addValue(identifier, system); 135 identifierDef.getChildByName("value").getMutator().addValue(identifier, value); 136 137 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t.getClass()); 138 resourceDef.getChildByName("identifier").getMutator().addValue(t, identifier); 139 }; 140 } 141 142 /** 143 * Set Organization.name 144 */ 145 default Consumer<IBaseResource> withName(String theStatus) { 146 return t -> __setPrimitiveChild(getFhirContext(), t, "name", "string", theStatus); 147 } 148 149 default Consumer<IBaseResource> withId(String theId) { 150 return t -> { 151 assertThat(theId, matchesPattern("[a-zA-Z0-9-]+")); 152 t.setId(theId); 153 }; 154 } 155 156 default Consumer<IBaseResource> withId(IIdType theId) { 157 return t -> t.setId(theId.toUnqualifiedVersionless()); 158 } 159 160 default Consumer<IBaseResource> withTag(String theSystem, String theCode) { 161 return t -> t.getMeta().addTag().setSystem(theSystem).setCode(theCode); 162 } 163 164 default Consumer<IBaseResource> withSecurity(String theSystem, String theCode) { 165 return t -> t.getMeta().addSecurity().setSystem(theSystem).setCode(theCode); 166 } 167 168 default Consumer<IBaseResource> withProfile(String theProfile) { 169 return t -> t.getMeta().addProfile(theProfile); 170 } 171 172 default Consumer<IBaseResource> withSource(FhirContext theContext, String theSource) { 173 return t -> MetaUtil.setSource(theContext, t.getMeta(), theSource); 174 } 175 176 default Consumer<IBaseResource> withLastUpdated(Date theLastUpdated) { 177 return t -> t.getMeta().setLastUpdated(theLastUpdated); 178 } 179 180 default Consumer<IBaseResource> withLastUpdated(String theIsoDate) { 181 return t -> t.getMeta().setLastUpdated(new InstantType(theIsoDate).getValue()); 182 } 183 184 default IIdType createEncounter(Consumer<IBaseResource>... theModifiers) { 185 return createResource("Encounter", theModifiers); 186 } 187 188 default IIdType createGroup(Consumer<IBaseResource>... theModifiers) { 189 return createResource("Group", theModifiers); 190 } 191 192 default IIdType createObservation(Consumer<IBaseResource>... theModifiers) { 193 return createResource("Observation", theModifiers); 194 } 195 196 default IIdType createObservation(Collection<Consumer<IBaseResource>> theModifiers) { 197 return createResource("Observation", theModifiers.toArray(new Consumer[0])); 198 } 199 200 default IBaseResource buildPatient(Consumer<IBaseResource>... theModifiers) { 201 return buildResource("Patient", theModifiers); 202 } 203 default IIdType createPatient(Consumer<IBaseResource>... theModifiers) { 204 return createResource("Patient", theModifiers); 205 } 206 207 default IIdType createOrganization(Consumer<IBaseResource>... theModifiers) { 208 return createResource("Organization", theModifiers); 209 } 210 211 default IIdType createResource(String theResourceType, Consumer<IBaseResource>... theModifiers) { 212 IBaseResource resource = buildResource(theResourceType, theModifiers); 213 214 if (ourLog.isDebugEnabled()) { 215 ourLog.debug("Creating {}", getFhirContext().newJsonParser().encodeResourceToString(resource)); 216 } 217 218 if (isNotBlank(resource.getIdElement().getValue())) { 219 return doUpdateResource(resource); 220 } else { 221 return doCreateResource(resource); 222 } 223 } 224 225 default IIdType createResourceFromJson(String theJson, Consumer<IBaseResource>... theModifiers) { 226 IBaseResource resource = getFhirContext().newJsonParser().parseResource(theJson); 227 applyElementModifiers(resource, theModifiers); 228 229 if (ourLog.isDebugEnabled()) { 230 ourLog.debug("Creating {}", getFhirContext().newJsonParser().encodeResourceToString(resource)); 231 } 232 233 if (isNotBlank(resource.getIdElement().getValue())) { 234 return doUpdateResource(resource); 235 } else { 236 return doCreateResource(resource); 237 } 238 } 239 240 default IBaseResource buildResource(String theResourceType, Consumer<IBaseResource>... theModifiers) { 241 IBaseResource resource = getFhirContext().getResourceDefinition(theResourceType).newInstance(); 242 applyElementModifiers(resource, theModifiers); 243 return resource; 244 } 245 246 247 default Consumer<IBaseResource> withSubject(@Nullable IIdType theSubject) { 248 return withReference("subject", theSubject); 249 } 250 251 default Consumer<IBaseResource> withSubject(@Nullable String theSubject) { 252 return withSubject(new IdType(theSubject)); 253 } 254 255 default Consumer<IBaseResource> withPatient(@Nullable IIdType theSubject) { 256 return withReference("patient", theSubject); 257 } 258 259 default Consumer<IBaseResource> withPatient(@Nullable String theSubject) { 260 return withSubject(new IdType(theSubject)); 261 } 262 263 default Consumer<IBaseResource> withGroupMember(@Nullable IIdType theMember) { 264 return withPrimitiveAttribute("member.entity.reference", theMember); 265 } 266 267 default Consumer<IBaseResource> withGroupMember(@Nullable String theMember) { 268 return withGroupMember(new IdType(theMember)); 269 } 270 271 default Consumer<IBaseResource> withEncounter(@Nullable String theEncounter) { 272 return withReference("encounter", new IdType(theEncounter)); 273 } 274 275 @Nonnull 276 private Consumer<IBaseResource> withReference(String theReferenceName, @Nullable IIdType theReferenceValue) { 277 return t -> { 278 if (theReferenceValue != null && theReferenceValue.getValue() != null) { 279 IBaseReference reference = (IBaseReference) getFhirContext().getElementDefinition("Reference").newInstance(); 280 reference.setReference(theReferenceValue.getValue()); 281 282 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(t); 283 resourceDef.getChildByName(theReferenceName).getMutator().addValue(t, reference); 284 } 285 }; 286 } 287 288 default <T extends IBase> Consumer<T> withPrimitiveAttribute(String thePath, Object theValue) { 289 return t->{ 290 FhirTerser terser = getFhirContext().newTerser(); 291 terser.addElement(t, thePath, ""+theValue); 292 }; 293 } 294 295 default <T extends IBase, E extends IBase> Consumer<T> withElementAt(String thePath, Consumer<E>... theModifiers) { 296 return t->{ 297 FhirTerser terser = getFhirContext().newTerser(); 298 E element = terser.addElement(t, thePath); 299 applyElementModifiers(element, theModifiers); 300 }; 301 } 302 303 default <T extends IBase> Consumer<T> withQuantityAtPath(String thePath, Number theValue, String theSystem, String theCode) { 304 return withElementAt(thePath, 305 withPrimitiveAttribute("value", theValue), 306 withPrimitiveAttribute("system", theSystem), 307 withPrimitiveAttribute("code", theCode) 308 ); 309 } 310 311 312 /** 313 * Create an Element and apply modifiers 314 * @param theElementType the FHIR Element type to create 315 * @param theModifiers modifiers to apply after construction 316 * @return the Element 317 */ 318 default IBase withElementOfType(String theElementType, Consumer<IBase>... theModifiers) { 319 IBase element = getFhirContext().getElementDefinition(theElementType).newInstance(); 320 applyElementModifiers(element, theModifiers); 321 return element; 322 } 323 324 default <E extends IBase> void applyElementModifiers(E element, Consumer<E>[] theModifiers) { 325 for (Consumer<E> nextModifier : theModifiers) { 326 nextModifier.accept(element); 327 } 328 } 329 330 default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode) { 331 return withObservationCode(theSystem, theCode, null); 332 } 333 334 default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode, @Nullable String theDisplay) { 335 return withCodingAt("code.coding", theSystem, theCode, theDisplay); 336 } 337 338 default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue) { 339 return withCodingAt(thePath, theSystem, theValue, null); 340 } 341 342 default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue, @Nullable String theDisplay) { 343 return withElementAt(thePath, 344 withPrimitiveAttribute("system", theSystem), 345 withPrimitiveAttribute("code", theValue), 346 withPrimitiveAttribute("display", theDisplay) 347 ); 348 } 349 350 default <T extends IBaseResource, E extends IBase> Consumer<T> withObservationComponent(Consumer<E>... theModifiers) { 351 return withElementAt("component", theModifiers); 352 } 353 354 default Consumer<IBaseResource> withObservationHasMember(@Nullable IIdType theHasMember) { 355 return withReference("hasMember", theHasMember); 356 } 357 358 default Consumer<IBaseResource> withOrganization(@Nullable IIdType theHasMember) { 359 return withReference("managingOrganization", theHasMember); 360 } 361 362 /** 363 * Users of this API must implement this method 364 */ 365 IIdType doCreateResource(IBaseResource theResource); 366 367 /** 368 * Users of this API must implement this method 369 */ 370 IIdType doUpdateResource(IBaseResource theResource); 371 372 /** 373 * Users of this API must implement this method 374 */ 375 FhirContext getFhirContext(); 376 377 /** 378 * Name chosen to avoid potential for conflict. This is an internal API to this interface. 379 */ 380 static void __setPrimitiveChild(FhirContext theFhirContext, IBaseResource theTarget, String theElementName, String theElementType, String theValue) { 381 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theTarget.getClass()); 382 BaseRuntimeChildDefinition activeChild = def.getChildByName(theElementName); 383 384 IPrimitiveType<?> booleanType = (IPrimitiveType<?>) activeChild.getChildByName(theElementName).newInstance(); 385 booleanType.setValueAsString(theValue); 386 activeChild.getMutator().addValue(theTarget, booleanType); 387 } 388 389 interface Support { 390 FhirContext getFhirContext(); 391 IIdType doCreateResource(IBaseResource theResource); 392 IIdType doUpdateResource(IBaseResource theResource); 393 } 394 395 interface WithSupport extends ITestDataBuilder { 396 Support getTestDataBuilderSupport(); 397 398 @Override 399 default FhirContext getFhirContext() { 400 return getTestDataBuilderSupport().getFhirContext(); 401 402 } 403 404 @Override 405 default IIdType doCreateResource(IBaseResource theResource) { 406 return getTestDataBuilderSupport().doCreateResource(theResource); 407 } 408 409 @Override 410 default IIdType doUpdateResource(IBaseResource theResource) { 411 return getTestDataBuilderSupport().doUpdateResource(theResource); 412 } 413 } 414 415 /** 416 * Dummy support to use ITestDataBuilder as just a builder, not a DAO 417 */ 418 class SupportNoDao implements Support { 419 final FhirContext myFhirContext; 420 421 public SupportNoDao(FhirContext theFhirContext) { 422 myFhirContext = theFhirContext; 423 } 424 425 @Override 426 public FhirContext getFhirContext() { 427 return myFhirContext; 428 } 429 430 @Override 431 public IIdType doCreateResource(IBaseResource theResource) { 432 Validate.isTrue(false, "Create not supported"); 433 return null; 434 } 435 436 @Override 437 public IIdType doUpdateResource(IBaseResource theResource) { 438 Validate.isTrue(false, "Update not supported"); 439 return null; 440 } 441 } 442}