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;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.FhirContext;
025import org.hl7.fhir.instance.model.api.IBase;
026import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
027import org.hl7.fhir.instance.model.api.IBaseResource;
028import org.hl7.fhir.instance.model.api.IPrimitiveType;
029
030import java.util.List;
031import java.util.Map;
032
033/**
034 * FHIR version independent representation of a Bundle Entry. This class can hold
035 * Bundle Entry data from any FHIR version (DSTU3+) and convert it back to the
036 * appropriate Bundle Entry type.
037 */
038// Created by Sonnet 4
039public class CanonicalBundleEntry {
040
041        private String myFullUrl;
042        private IBaseResource myResource;
043
044        // Request fields
045        private String myRequestMethod;
046        private String myRequestUrl;
047        private String myRequestIfNoneMatch;
048        private String myRequestIfModifiedSince;
049        private String myRequestIfMatch;
050        private String myRequestIfNoneExist;
051
052        // Response fields
053        private String myResponseStatus;
054        private String myResponseLocation;
055        private String myResponseEtag;
056        private String myResponseLastModified;
057
058        // Search fields
059        private String mySearchMode;
060        private String mySearchScore;
061
062        // Link fields (less common but part of spec)
063        private List<Map<String, String>> myLinks;
064
065        public CanonicalBundleEntry() {
066                // Default constructor
067        }
068
069        public String getFullUrl() {
070                return myFullUrl;
071        }
072
073        public void setFullUrl(String theFullUrl) {
074                myFullUrl = theFullUrl;
075        }
076
077        public IBaseResource getResource() {
078                return myResource;
079        }
080
081        public void setResource(IBaseResource theResource) {
082                myResource = theResource;
083        }
084
085        public String getRequestMethod() {
086                return myRequestMethod;
087        }
088
089        public void setRequestMethod(String theRequestMethod) {
090                myRequestMethod = theRequestMethod;
091        }
092
093        public String getRequestUrl() {
094                return myRequestUrl;
095        }
096
097        public void setRequestUrl(String theRequestUrl) {
098                myRequestUrl = theRequestUrl;
099        }
100
101        public String getRequestIfNoneMatch() {
102                return myRequestIfNoneMatch;
103        }
104
105        public void setRequestIfNoneMatch(String theRequestIfNoneMatch) {
106                myRequestIfNoneMatch = theRequestIfNoneMatch;
107        }
108
109        public String getRequestIfModifiedSince() {
110                return myRequestIfModifiedSince;
111        }
112
113        public void setRequestIfModifiedSince(String theRequestIfModifiedSince) {
114                myRequestIfModifiedSince = theRequestIfModifiedSince;
115        }
116
117        public String getRequestIfMatch() {
118                return myRequestIfMatch;
119        }
120
121        public void setRequestIfMatch(String theRequestIfMatch) {
122                myRequestIfMatch = theRequestIfMatch;
123        }
124
125        public String getRequestIfNoneExist() {
126                return myRequestIfNoneExist;
127        }
128
129        public void setRequestIfNoneExist(String theRequestIfNoneExist) {
130                myRequestIfNoneExist = theRequestIfNoneExist;
131        }
132
133        public String getResponseStatus() {
134                return myResponseStatus;
135        }
136
137        public void setResponseStatus(String theResponseStatus) {
138                myResponseStatus = theResponseStatus;
139        }
140
141        public String getResponseLocation() {
142                return myResponseLocation;
143        }
144
145        public void setResponseLocation(String theResponseLocation) {
146                myResponseLocation = theResponseLocation;
147        }
148
149        public String getResponseEtag() {
150                return myResponseEtag;
151        }
152
153        public void setResponseEtag(String theResponseEtag) {
154                myResponseEtag = theResponseEtag;
155        }
156
157        public String getResponseLastModified() {
158                return myResponseLastModified;
159        }
160
161        public void setResponseLastModified(String theResponseLastModified) {
162                myResponseLastModified = theResponseLastModified;
163        }
164
165        public String getSearchMode() {
166                return mySearchMode;
167        }
168
169        public void setSearchMode(String theSearchMode) {
170                mySearchMode = theSearchMode;
171        }
172
173        public String getSearchScore() {
174                return mySearchScore;
175        }
176
177        public void setSearchScore(String theSearchScore) {
178                mySearchScore = theSearchScore;
179        }
180
181        public List<Map<String, String>> getLinks() {
182                return myLinks;
183        }
184
185        public void setLinks(List<Map<String, String>> theLinks) {
186                myLinks = theLinks;
187        }
188
189        /**
190         * Factory method to create a CanonicalBundleEntry from a Bundle Entry
191         * @param theFhirContext The FHIR context
192         * @param theEntry The Bundle Entry to convert
193         * @return A new CanonicalBundleEntry instance
194         */
195        public static CanonicalBundleEntry fromBundleEntry(FhirContext theFhirContext, IBaseBackboneElement theEntry) {
196                CanonicalBundleEntry retVal = new CanonicalBundleEntry();
197
198                FhirTerser terser = theFhirContext.newTerser();
199
200                // Extract fullUrl
201                IPrimitiveType<?> fullUrlElement = terser.getSingleValueOrNull(theEntry, "fullUrl", IPrimitiveType.class);
202                if (fullUrlElement != null) {
203                        retVal.setFullUrl(fullUrlElement.getValueAsString());
204                }
205
206                // Extract resource
207                IBaseResource resource = terser.getSingleValueOrNull(theEntry, "resource", IBaseResource.class);
208                if (resource != null) {
209                        retVal.setResource(resource);
210                }
211
212                // Extract request fields
213                IBaseBackboneElement request = terser.getSingleValueOrNull(theEntry, "request", IBaseBackboneElement.class);
214                if (request != null) {
215                        IPrimitiveType<?> method = terser.getSingleValueOrNull(request, "method", IPrimitiveType.class);
216                        if (method != null) {
217                                retVal.setRequestMethod(method.getValueAsString());
218                        }
219
220                        IPrimitiveType<?> url = terser.getSingleValueOrNull(request, "url", IPrimitiveType.class);
221                        if (url != null) {
222                                retVal.setRequestUrl(url.getValueAsString());
223                        }
224
225                        IPrimitiveType<?> ifNoneMatch = terser.getSingleValueOrNull(request, "ifNoneMatch", IPrimitiveType.class);
226                        if (ifNoneMatch != null) {
227                                retVal.setRequestIfNoneMatch(ifNoneMatch.getValueAsString());
228                        }
229
230                        IPrimitiveType<?> ifModifiedSince =
231                                        terser.getSingleValueOrNull(request, "ifModifiedSince", IPrimitiveType.class);
232                        if (ifModifiedSince != null) {
233                                retVal.setRequestIfModifiedSince(ifModifiedSince.getValueAsString());
234                        }
235
236                        IPrimitiveType<?> ifMatch = terser.getSingleValueOrNull(request, "ifMatch", IPrimitiveType.class);
237                        if (ifMatch != null) {
238                                retVal.setRequestIfMatch(ifMatch.getValueAsString());
239                        }
240
241                        IPrimitiveType<?> ifNoneExist = terser.getSingleValueOrNull(request, "ifNoneExist", IPrimitiveType.class);
242                        if (ifNoneExist != null) {
243                                retVal.setRequestIfNoneExist(ifNoneExist.getValueAsString());
244                        }
245                }
246
247                // Extract response fields
248                IBaseBackboneElement response = terser.getSingleValueOrNull(theEntry, "response", IBaseBackboneElement.class);
249                if (response != null) {
250                        IPrimitiveType<?> status = terser.getSingleValueOrNull(response, "status", IPrimitiveType.class);
251                        if (status != null) {
252                                retVal.setResponseStatus(status.getValueAsString());
253                        }
254
255                        IPrimitiveType<?> location = terser.getSingleValueOrNull(response, "location", IPrimitiveType.class);
256                        if (location != null) {
257                                retVal.setResponseLocation(location.getValueAsString());
258                        }
259
260                        IPrimitiveType<?> etag = terser.getSingleValueOrNull(response, "etag", IPrimitiveType.class);
261                        if (etag != null) {
262                                retVal.setResponseEtag(etag.getValueAsString());
263                        }
264
265                        IPrimitiveType<?> lastModified =
266                                        terser.getSingleValueOrNull(response, "lastModified", IPrimitiveType.class);
267                        if (lastModified != null) {
268                                retVal.setResponseLastModified(lastModified.getValueAsString());
269                        }
270                }
271
272                // Extract search fields
273                IBaseBackboneElement search = terser.getSingleValueOrNull(theEntry, "search", IBaseBackboneElement.class);
274                if (search != null) {
275                        IPrimitiveType<?> mode = terser.getSingleValueOrNull(search, "mode", IPrimitiveType.class);
276                        if (mode != null) {
277                                retVal.setSearchMode(mode.getValueAsString());
278                        }
279
280                        IPrimitiveType<?> score = terser.getSingleValueOrNull(search, "score", IPrimitiveType.class);
281                        if (score != null) {
282                                retVal.setSearchScore(score.getValueAsString());
283                        }
284                }
285
286                return retVal;
287        }
288
289        /**
290         * Convert this CanonicalBundleEntry back to a Bundle Entry of the specified type
291         * @param theFhirContext The FHIR context
292         * @param theBundleEntryComponentClass The target Bundle Entry class
293         * @return A new Bundle Entry instance
294         */
295        public <T extends IBase> T toBundleEntry(FhirContext theFhirContext, Class<T> theBundleEntryComponentClass) {
296                @SuppressWarnings("unchecked")
297                T entry = (T) theFhirContext
298                                .getElementDefinition(theBundleEntryComponentClass)
299                                .newInstance();
300
301                // Get the element definition for the Bundle Entry
302                BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>)
303                                theFhirContext.getElementDefinition(theBundleEntryComponentClass);
304
305                // Set fullUrl
306                if (myFullUrl != null) {
307                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
308                        if (fullUrlChild != null) {
309                                IPrimitiveType<?> fullUrlValue = (IPrimitiveType<?>)
310                                                fullUrlChild.getChildByName("fullUrl").newInstance();
311                                fullUrlValue.setValueAsString(myFullUrl);
312                                fullUrlChild.getMutator().setValue(entry, fullUrlValue);
313                        }
314                }
315
316                // Set resource
317                if (myResource != null) {
318                        BaseRuntimeChildDefinition resourceChild = entryDef.getChildByName("resource");
319                        if (resourceChild != null) {
320                                resourceChild.getMutator().setValue(entry, myResource);
321                        }
322                }
323
324                // Set request fields
325                if (myRequestMethod != null
326                                || myRequestUrl != null
327                                || myRequestIfNoneMatch != null
328                                || myRequestIfModifiedSince != null
329                                || myRequestIfMatch != null
330                                || myRequestIfNoneExist != null) {
331
332                        BaseRuntimeChildDefinition requestChild = entryDef.getChildByName("request");
333                        if (requestChild != null) {
334                                IBase request = requestChild.getChildByName("request").newInstance();
335                                requestChild.getMutator().setValue(entry, request);
336
337                                BaseRuntimeElementCompositeDefinition<?> requestDef =
338                                                (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
339
340                                if (myRequestMethod != null) {
341                                        BaseRuntimeChildDefinition methodChild = requestDef.getChildByName("method");
342                                        if (methodChild != null) {
343                                                IPrimitiveType<?> methodValue = (IPrimitiveType<?>) methodChild
344                                                                .getChildByName("method")
345                                                                .newInstance(methodChild.getInstanceConstructorArguments());
346                                                methodValue.setValueAsString(myRequestMethod);
347                                                methodChild.getMutator().setValue(request, methodValue);
348                                        }
349                                }
350
351                                if (myRequestUrl != null) {
352                                        BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
353                                        if (urlChild != null) {
354                                                IPrimitiveType<?> urlValue = (IPrimitiveType<?>)
355                                                                urlChild.getChildByName("url").newInstance();
356                                                urlValue.setValueAsString(myRequestUrl);
357                                                urlChild.getMutator().setValue(request, urlValue);
358                                        }
359                                }
360
361                                if (myRequestIfNoneMatch != null) {
362                                        BaseRuntimeChildDefinition ifNoneMatchChild = requestDef.getChildByName("ifNoneMatch");
363                                        if (ifNoneMatchChild != null) {
364                                                IPrimitiveType<?> ifNoneMatchValue = (IPrimitiveType<?>)
365                                                                ifNoneMatchChild.getChildByName("ifNoneMatch").newInstance();
366                                                ifNoneMatchValue.setValueAsString(myRequestIfNoneMatch);
367                                                ifNoneMatchChild.getMutator().setValue(request, ifNoneMatchValue);
368                                        }
369                                }
370
371                                if (myRequestIfModifiedSince != null) {
372                                        BaseRuntimeChildDefinition ifModifiedSinceChild = requestDef.getChildByName("ifModifiedSince");
373                                        if (ifModifiedSinceChild != null) {
374                                                IPrimitiveType<?> ifModifiedSinceValue = (IPrimitiveType<?>) ifModifiedSinceChild
375                                                                .getChildByName("ifModifiedSince")
376                                                                .newInstance();
377                                                ifModifiedSinceValue.setValueAsString(myRequestIfModifiedSince);
378                                                ifModifiedSinceChild.getMutator().setValue(request, ifModifiedSinceValue);
379                                        }
380                                }
381
382                                if (myRequestIfMatch != null) {
383                                        BaseRuntimeChildDefinition ifMatchChild = requestDef.getChildByName("ifMatch");
384                                        if (ifMatchChild != null) {
385                                                IPrimitiveType<?> ifMatchValue = (IPrimitiveType<?>)
386                                                                ifMatchChild.getChildByName("ifMatch").newInstance();
387                                                ifMatchValue.setValueAsString(myRequestIfMatch);
388                                                ifMatchChild.getMutator().setValue(request, ifMatchValue);
389                                        }
390                                }
391
392                                if (myRequestIfNoneExist != null) {
393                                        BaseRuntimeChildDefinition ifNoneExistChild = requestDef.getChildByName("ifNoneExist");
394                                        if (ifNoneExistChild != null) {
395                                                IPrimitiveType<?> ifNoneExistValue = (IPrimitiveType<?>)
396                                                                ifNoneExistChild.getChildByName("ifNoneExist").newInstance();
397                                                ifNoneExistValue.setValueAsString(myRequestIfNoneExist);
398                                                ifNoneExistChild.getMutator().setValue(request, ifNoneExistValue);
399                                        }
400                                }
401                        }
402                }
403
404                // Set response fields
405                if (myResponseStatus != null
406                                || myResponseLocation != null
407                                || myResponseEtag != null
408                                || myResponseLastModified != null) {
409                        BaseRuntimeChildDefinition responseChild = entryDef.getChildByName("response");
410                        if (responseChild != null) {
411                                IBase response = responseChild.getChildByName("response").newInstance();
412                                responseChild.getMutator().setValue(entry, response);
413
414                                BaseRuntimeElementCompositeDefinition<?> responseDef =
415                                                (BaseRuntimeElementCompositeDefinition<?>) responseChild.getChildByName("response");
416
417                                if (myResponseStatus != null) {
418                                        BaseRuntimeChildDefinition statusChild = responseDef.getChildByName("status");
419                                        if (statusChild != null) {
420                                                IPrimitiveType<?> statusValue = (IPrimitiveType<?>)
421                                                                statusChild.getChildByName("status").newInstance();
422                                                statusValue.setValueAsString(myResponseStatus);
423                                                statusChild.getMutator().setValue(response, statusValue);
424                                        }
425                                }
426
427                                if (myResponseLocation != null) {
428                                        BaseRuntimeChildDefinition locationChild = responseDef.getChildByName("location");
429                                        if (locationChild != null) {
430                                                IPrimitiveType<?> locationValue = (IPrimitiveType<?>)
431                                                                locationChild.getChildByName("location").newInstance();
432                                                locationValue.setValueAsString(myResponseLocation);
433                                                locationChild.getMutator().setValue(response, locationValue);
434                                        }
435                                }
436
437                                if (myResponseEtag != null) {
438                                        BaseRuntimeChildDefinition etagChild = responseDef.getChildByName("etag");
439                                        if (etagChild != null) {
440                                                IPrimitiveType<?> etagValue = (IPrimitiveType<?>)
441                                                                etagChild.getChildByName("etag").newInstance();
442                                                etagValue.setValueAsString(myResponseEtag);
443                                                etagChild.getMutator().setValue(response, etagValue);
444                                        }
445                                }
446
447                                if (myResponseLastModified != null) {
448                                        BaseRuntimeChildDefinition lastModifiedChild = responseDef.getChildByName("lastModified");
449                                        if (lastModifiedChild != null) {
450                                                IPrimitiveType<?> lastModifiedValue = (IPrimitiveType<?>)
451                                                                lastModifiedChild.getChildByName("lastModified").newInstance();
452                                                lastModifiedValue.setValueAsString(myResponseLastModified);
453                                                lastModifiedChild.getMutator().setValue(response, lastModifiedValue);
454                                        }
455                                }
456                        }
457                }
458
459                // Set search fields
460                if (mySearchMode != null || mySearchScore != null) {
461                        BaseRuntimeChildDefinition searchChild = entryDef.getChildByName("search");
462                        if (searchChild != null) {
463                                IBase search = searchChild.getChildByName("search").newInstance();
464                                searchChild.getMutator().setValue(entry, search);
465
466                                BaseRuntimeElementCompositeDefinition<?> searchDef =
467                                                (BaseRuntimeElementCompositeDefinition<?>) searchChild.getChildByName("search");
468
469                                if (mySearchMode != null) {
470                                        BaseRuntimeChildDefinition modeChild = searchDef.getChildByName("mode");
471                                        if (modeChild != null) {
472                                                IPrimitiveType<?> modeValue = (IPrimitiveType<?>)
473                                                                modeChild.getChildByName("mode").newInstance();
474                                                modeValue.setValueAsString(mySearchMode);
475                                                modeChild.getMutator().setValue(search, modeValue);
476                                        }
477                                }
478
479                                if (mySearchScore != null) {
480                                        BaseRuntimeChildDefinition scoreChild = searchDef.getChildByName("score");
481                                        if (scoreChild != null) {
482                                                IPrimitiveType<?> scoreValue = (IPrimitiveType<?>)
483                                                                scoreChild.getChildByName("score").newInstance();
484                                                scoreValue.setValueAsString(mySearchScore);
485                                                scoreChild.getMutator().setValue(search, scoreValue);
486                                        }
487                                }
488                        }
489                }
490
491                return entry;
492        }
493}