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