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}