001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.util.bundle;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.util.ElementUtil;
026import jakarta.annotation.Nonnull;
027import jakarta.annotation.Nullable;
028import org.hl7.fhir.instance.model.api.IBase;
029import org.hl7.fhir.instance.model.api.IBaseResource;
030import org.hl7.fhir.instance.model.api.IPrimitiveType;
031
032import java.util.Date;
033import java.util.Objects;
034import java.util.function.Function;
035
036import static ca.uhn.fhir.util.ElementUtil.getSingleValueOrNull;
037import static ca.uhn.fhir.util.ElementUtil.setValue;
038
039/**
040 * Components of a transaction-respose bundle entry.
041 */
042public record BundleResponseEntryParts(
043                String fullUrl,
044                IBaseResource resource,
045                String responseStatus,
046                String responseLocation,
047                String responseEtag,
048                IPrimitiveType<Date> responseLastModified,
049                IBaseResource responseOutcome) {
050
051        static class Metadata implements PartsConverter<BundleResponseEntryParts> {
052
053                private final BaseRuntimeChildDefinition myFullUrlChildDef;
054                private final BaseRuntimeChildDefinition myResourceChildDef;
055                private final BaseRuntimeChildDefinition myResponseChildDef;
056                private final BaseRuntimeChildDefinition myResponseOutcomeChildDef;
057                private final BaseRuntimeChildDefinition myResponseStatusChildDef;
058                private final BaseRuntimeChildDefinition myResponseLocation;
059                private final BaseRuntimeChildDefinition myResponseEtag;
060                private final BaseRuntimeChildDefinition myResponseLastModified;
061                private final BaseRuntimeElementCompositeDefinition<?> myEntryElementDef;
062                private final BaseRuntimeElementCompositeDefinition<?> myResponseChildContentsDef;
063
064                private Metadata(FhirContext theFhirContext) {
065
066                        BaseRuntimeChildDefinition entryChildDef =
067                                        theFhirContext.getResourceDefinition("Bundle").getChildByName("entry");
068
069                        myEntryElementDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
070
071                        myFullUrlChildDef = myEntryElementDef.getChildByName("fullUrl");
072                        myResourceChildDef = myEntryElementDef.getChildByName("resource");
073
074                        myResponseChildDef = myEntryElementDef.getChildByName("response");
075
076                        myResponseChildContentsDef =
077                                        (BaseRuntimeElementCompositeDefinition<?>) myResponseChildDef.getChildByName("response");
078
079                        myResponseOutcomeChildDef = myResponseChildContentsDef.getChildByName("outcome");
080                        myResponseStatusChildDef = myResponseChildContentsDef.getChildByName("status");
081                        myResponseLocation = myResponseChildContentsDef.getChildByName("location");
082                        myResponseEtag = myResponseChildContentsDef.getChildByName("etag");
083                        myResponseLastModified = myResponseChildContentsDef.getChildByName("lastModified");
084                }
085
086                @Override
087                @Nullable
088                public BundleResponseEntryParts fromElement(IBase base) {
089                        if (base == null) {
090                                return null;
091                        }
092
093                        IBase response = getSingleValueOrNull(base, myResponseChildDef, Function.identity());
094
095                        return new BundleResponseEntryParts(
096                                        getSingleValueOrNull(base, myFullUrlChildDef, ElementUtil.CONVERT_PRIMITIVE_TO_STRING),
097                                        getSingleValueOrNull(base, myResourceChildDef, ElementUtil.CAST_BASE_TO_RESOURCE),
098                                        getSingleValueOrNull(response, myResponseStatusChildDef, ElementUtil.CONVERT_PRIMITIVE_TO_STRING),
099                                        getSingleValueOrNull(response, myResponseLocation, ElementUtil.CONVERT_PRIMITIVE_TO_STRING),
100                                        getSingleValueOrNull(response, myResponseEtag, ElementUtil.CONVERT_PRIMITIVE_TO_STRING),
101                                        getSingleValueOrNull(response, myResponseLastModified, ElementUtil.CAST_TO_PRIMITIVE_DATE),
102                                        getSingleValueOrNull(response, myResponseOutcomeChildDef, ElementUtil.CAST_BASE_TO_RESOURCE));
103                }
104
105                @Override
106                public IBase toElement(BundleResponseEntryParts theParts) {
107                        Objects.requireNonNull(theParts);
108                        IBase entry = myEntryElementDef.newInstance();
109
110                        setValue(entry, myFullUrlChildDef, theParts.fullUrl());
111                        setValue(entry, myResourceChildDef, theParts.resource());
112
113                        // response parts
114                        IBase response = myResponseChildContentsDef.newInstance();
115                        setValue(entry, myResponseChildDef, response);
116
117                        setValue(response, myResponseStatusChildDef, theParts.responseStatus());
118                        setValue(response, myResponseLocation, theParts.responseLocation());
119                        setValue(response, myResponseEtag, theParts.responseEtag());
120                        setValue(response, myResponseLastModified, theParts.responseLastModified());
121                        setValue(response, myResponseOutcomeChildDef, theParts.responseOutcome());
122
123                        return entry;
124                }
125        }
126
127        /**
128         * Build an extractor function that can be used to extract the parts of a bundle entry.
129         * @param theFhirContext for the mappings
130         * @return an extractor function on IBase objects that returns a BundleResponseEntryParts object
131         */
132        public static Function<IBase, BundleResponseEntryParts> buildPartsExtractor(FhirContext theFhirContext) {
133                PartsConverter<BundleResponseEntryParts> m = getConverter(theFhirContext);
134                return m::fromElement;
135        }
136
137        @Nonnull
138        public static PartsConverter<BundleResponseEntryParts> getConverter(FhirContext theFhirContext) {
139                return new Metadata(theFhirContext);
140        }
141
142        public static Function<BundleResponseEntryParts, IBase> builder(FhirContext theFhirContext) {
143                PartsConverter<BundleResponseEntryParts> m = getConverter(theFhirContext);
144
145                return m::toElement;
146        }
147}