001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2023 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.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.FhirContext;
026import ca.uhn.fhir.context.RuntimeResourceDefinition;
027import ca.uhn.fhir.i18n.Msg;
028import ca.uhn.fhir.rest.api.PatchTypeEnum;
029import ca.uhn.fhir.rest.api.RequestTypeEnum;
030import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
031import ca.uhn.fhir.util.bundle.BundleEntryMutator;
032import ca.uhn.fhir.util.bundle.BundleEntryParts;
033import ca.uhn.fhir.util.bundle.EntryListAccumulator;
034import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
035import ca.uhn.fhir.util.bundle.SearchBundleEntryParts;
036import org.apache.commons.lang3.tuple.Pair;
037import org.hl7.fhir.instance.model.api.IBase;
038import org.hl7.fhir.instance.model.api.IBaseBinary;
039import org.hl7.fhir.instance.model.api.IBaseBundle;
040import org.hl7.fhir.instance.model.api.IBaseResource;
041import org.hl7.fhir.instance.model.api.IPrimitiveType;
042
043import java.util.ArrayList;
044import java.util.HashMap;
045import java.util.LinkedHashSet;
046import java.util.List;
047import java.util.Map;
048import java.util.Objects;
049import java.util.Set;
050import java.util.function.Consumer;
051import java.util.stream.Collectors;
052
053import static org.apache.commons.lang3.StringUtils.isNotBlank;
054/**
055 * Fetch resources from a bundle
056 */
057public class BundleUtil {
058        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BundleUtil.class);
059
060
061        /**
062         * @return Returns <code>null</code> if the link isn't found or has no value
063         */
064        public static String getLinkUrlOfType(FhirContext theContext, IBaseBundle theBundle, String theLinkRelation) {
065                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
066                BaseRuntimeChildDefinition entryChild = def.getChildByName("link");
067                List<IBase> links = entryChild.getAccessor().getValues(theBundle);
068                for (IBase nextLink : links) {
069
070                        boolean isRightRel = false;
071                        BaseRuntimeElementCompositeDefinition relDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
072                        BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation");
073                        List<IBase> relValues = relChild.getAccessor().getValues(nextLink);
074                        for (IBase next : relValues) {
075                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) next;
076                                if (theLinkRelation.equals(nextValue.getValueAsString())) {
077                                        isRightRel = true;
078                                }
079                        }
080
081                        if (!isRightRel) {
082                                continue;
083                        }
084
085                        BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
086                        BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url");
087                        List<IBase> values = urlChild.getAccessor().getValues(nextLink);
088                        for (IBase nextUrl : values) {
089                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextUrl;
090                                if (isNotBlank(nextValue.getValueAsString())) {
091                                        return nextValue.getValueAsString();
092                                }
093                        }
094
095                }
096
097                return null;
098        }
099
100        @SuppressWarnings("unchecked")
101        public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) {
102                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
103                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
104                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
105
106                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
107                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
108
109                BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
110                BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
111
112                BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
113
114                List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size());
115                for (IBase nextEntry : entries) {
116
117                        String url = null;
118                        IBaseResource resource = null;
119
120                        for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) {
121                                for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) {
122                                        url = ((IPrimitiveType<String>) nextUrlValue).getValue();
123                                }
124                        }
125
126                        // Should return 0..1 only
127                        for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) {
128                                resource = (IBaseResource) nextValue;
129                        }
130
131                        retVal.add(Pair.of(url, resource));
132                }
133
134                return retVal;
135        }
136
137        public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) {
138                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
139                BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
140                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
141                if (entries.size() > 0) {
142                        IPrimitiveType<?> typeElement = (IPrimitiveType<?>) entries.get(0);
143                        return typeElement.getValueAsString();
144                }
145                return null;
146        }
147
148        public static void setBundleType(FhirContext theContext, IBaseBundle theBundle, String theType) {
149                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
150                BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
151                BaseRuntimeElementDefinition<?> element = entryChild.getChildByName("type");
152                IPrimitiveType<?> typeInstance = (IPrimitiveType<?>) element.newInstance(entryChild.getInstanceConstructorArguments());
153                typeInstance.setValueAsString(theType);
154
155                entryChild.getMutator().setValue(theBundle, typeInstance);
156        }
157
158        public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) {
159                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
160                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
161                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
162                if (entries.size() > 0) {
163                        IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0);
164                        if (typeElement != null && typeElement.getValue() != null) {
165                                return typeElement.getValue().intValue();
166                        }
167                }
168                return null;
169        }
170
171        public static void setTotal(FhirContext theContext, IBaseBundle theBundle, Integer theTotal) {
172                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
173                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
174                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) entryChild.getChildByName("total").newInstance();
175                value.setValue(theTotal);
176                entryChild.getMutator().setValue(theBundle, value);
177        }
178
179        /**
180         * Extract all of the resources from a given bundle
181         */
182        public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
183                EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
184                processEntries(theContext, theBundle, entryListAccumulator);
185                return entryListAccumulator.getList();
186        }
187
188        static int WHITE = 1;
189        static int GRAY = 2;
190        static int BLACK = 3;
191
192        /**
193         * Function which will do an in-place sort of a bundles' entries, to the correct processing order, which is:
194         * 1. Deletes
195         * 2. Creates
196         * 3. Updates
197         *
198         * Furthermore, within these operation types, the entries will be sorted based on the order in which they should be processed
199         * e.g. if you have 2 CREATEs, one for a Patient, and one for an Observation which has this Patient as its Subject,
200         * the patient will come first, then the observation.
201         *
202         * In cases of there being a cyclic dependency (e.g. Organization/1 is partOf Organization/2 and Organization/2 is partOf Organization/1)
203         * this function will throw an IllegalStateException.
204         *
205         * @param theContext The FhirContext.
206         * @param theBundle The {@link IBaseBundle} which contains the entries you would like sorted into processing order.
207         */
208        public static void sortEntriesIntoProcessingOrder(FhirContext theContext, IBaseBundle theBundle) throws IllegalStateException {
209                Map<BundleEntryParts, IBase> partsToIBaseMap = getPartsToIBaseMap(theContext, theBundle);
210                LinkedHashSet<IBase> retVal = new LinkedHashSet<>();
211
212                //Get all deletions.
213                LinkedHashSet<IBase> deleteParts = sortEntriesOfTypeIntoProcessingOrder(theContext, RequestTypeEnum.DELETE, partsToIBaseMap);
214                validatePartsNotNull(deleteParts);
215                retVal.addAll(deleteParts);
216
217                //Get all Creations
218                LinkedHashSet<IBase> createParts= sortEntriesOfTypeIntoProcessingOrder(theContext, RequestTypeEnum.POST, partsToIBaseMap);
219                validatePartsNotNull(createParts);
220                retVal.addAll(createParts);
221
222                // Get all Updates
223                LinkedHashSet<IBase> updateParts= sortEntriesOfTypeIntoProcessingOrder(theContext, RequestTypeEnum.PUT, partsToIBaseMap);
224                validatePartsNotNull(updateParts);
225                retVal.addAll(updateParts);
226
227                //Once we are done adding all DELETE, POST, PUT operations, add everything else.
228                //Since this is a set, it will just fail to add already-added operations.
229                retVal.addAll(partsToIBaseMap.values());
230
231                //Blow away the entries and reset them in the right order.
232                TerserUtil.clearField(theContext, theBundle, "entry");
233                TerserUtil.setField(theContext, "entry", theBundle, retVal.toArray(new IBase[0]));
234        }
235
236        private static void validatePartsNotNull(LinkedHashSet<IBase> theDeleteParts) {
237                if (theDeleteParts == null) {
238                        throw new IllegalStateException(Msg.code(1745) + "This transaction contains a cycle, so it cannot be sorted.");
239                }
240        }
241
242        private static LinkedHashSet<IBase> sortEntriesOfTypeIntoProcessingOrder(FhirContext theContext, RequestTypeEnum theRequestTypeEnum, Map<BundleEntryParts, IBase> thePartsToIBaseMap) {
243                SortLegality legality = new SortLegality();
244                HashMap<String, Integer> color = new HashMap<>();
245                HashMap<String, List<String>> adjList = new HashMap<>();
246                List<String> topologicalOrder = new ArrayList<>();
247                Set<BundleEntryParts> bundleEntryParts = thePartsToIBaseMap.keySet().stream().filter(part -> part.getRequestType().equals(theRequestTypeEnum)).collect(Collectors.toSet());
248                HashMap<String, BundleEntryParts> resourceIdToBundleEntryMap = new HashMap<>();
249
250                for (BundleEntryParts bundleEntryPart : bundleEntryParts) {
251                        IBaseResource resource = bundleEntryPart.getResource();
252                        if (resource != null) {
253                                String resourceId = resource.getIdElement().toVersionless().toString();
254                                resourceIdToBundleEntryMap.put(resourceId, bundleEntryPart);
255                                if (resourceId == null) {
256                                        if (bundleEntryPart.getFullUrl() != null) {
257                                                resourceId = bundleEntryPart.getFullUrl();
258                                        }
259                                }
260
261                                color.put(resourceId, WHITE);
262                        }
263                }
264
265                for (BundleEntryParts bundleEntryPart : bundleEntryParts) {
266                        IBaseResource resource = bundleEntryPart.getResource();
267                        if (resource != null) {
268                                String resourceId = resource.getIdElement().toVersionless().toString();
269                                resourceIdToBundleEntryMap.put(resourceId, bundleEntryPart);
270                                if (resourceId == null) {
271                                        if (bundleEntryPart.getFullUrl() != null) {
272                                                resourceId = bundleEntryPart.getFullUrl();
273                                        }
274                                }
275                                List<ResourceReferenceInfo> allResourceReferences = theContext.newTerser().getAllResourceReferences(resource);
276                                String finalResourceId = resourceId;
277                                allResourceReferences
278                                        .forEach(refInfo -> {
279                                                String referencedResourceId = refInfo.getResourceReference().getReferenceElement().toVersionless().getValue();
280                                                if (color.containsKey(referencedResourceId)) {
281                                                        if (!adjList.containsKey(finalResourceId)) {
282                                                                adjList.put(finalResourceId, new ArrayList<>());
283                                                        }
284                                                        adjList.get(finalResourceId).add(referencedResourceId);
285                                                }
286                                        });
287                        }
288                }
289
290                for (Map.Entry<String, Integer> entry:color.entrySet()) {
291                        if (entry.getValue() == WHITE) {
292                                depthFirstSearch(entry.getKey(), color, adjList, topologicalOrder, legality);
293                        }
294                }
295
296                if (legality.isLegal()) {
297                        if (ourLog.isDebugEnabled()) {
298                                ourLog.debug("Topological order is: {}", String.join(",", topologicalOrder));
299                        }
300
301                        LinkedHashSet<IBase> orderedEntries = new LinkedHashSet<>();
302                        for (int i = 0; i < topologicalOrder.size(); i++) {
303                                BundleEntryParts bep;
304                                if (theRequestTypeEnum.equals(RequestTypeEnum.DELETE)) {
305                                        int index = topologicalOrder.size() - i - 1;
306                                        bep = resourceIdToBundleEntryMap.get(topologicalOrder.get(index));
307                                } else {
308                                        bep = resourceIdToBundleEntryMap.get(topologicalOrder.get(i));
309                                }
310                                IBase base = thePartsToIBaseMap.get(bep);
311                                orderedEntries.add(base);
312                        }
313
314                        return orderedEntries;
315
316                } else {
317                        return null;
318                }
319        }
320
321        private static void depthFirstSearch(String theResourceId, HashMap<String, Integer> theResourceIdToColor, HashMap<String, List<String>> theAdjList, List<String> theTopologicalOrder, SortLegality theLegality) {
322
323                if (!theLegality.isLegal()) {
324                        ourLog.debug("Found a cycle while trying to sort bundle entries. This bundle is not sortable.");
325                        return;
326                }
327
328                //We are currently recursing over this node (gray)
329                theResourceIdToColor.put(theResourceId, GRAY);
330
331                for (String neighbourResourceId: theAdjList.getOrDefault(theResourceId, new ArrayList<>())) {
332                        if (theResourceIdToColor.get(neighbourResourceId) == WHITE) {
333                                depthFirstSearch(neighbourResourceId, theResourceIdToColor, theAdjList, theTopologicalOrder, theLegality);
334                        } else if (theResourceIdToColor.get(neighbourResourceId) == GRAY) {
335                                theLegality.setLegal(false);
336                                return;
337                        }
338                }
339                //Mark the node as black
340                theResourceIdToColor.put(theResourceId, BLACK);
341                theTopologicalOrder.add(theResourceId);
342        }
343
344        private static Map<BundleEntryParts, IBase> getPartsToIBaseMap(FhirContext theContext, IBaseBundle theBundle) {
345                RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
346                BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
347                List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
348
349                BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
350                BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl");
351                BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
352                BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
353                BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
354                BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
355                BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
356                BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
357                Map<BundleEntryParts, IBase> map = new HashMap<>();
358                for (IBase nextEntry : entries) {
359                        BundleEntryParts parts = getBundleEntryParts(fullUrlChildDef, resourceChildDef, requestChildDef, requestUrlChildDef, requestIfNoneExistChildDef, methodChildDef, nextEntry);
360                        /*
361                         * All 3 might be null - That's ok because we still want to know the
362                         * order in the original bundle.
363                         */
364                        map.put(parts, nextEntry);
365                }
366                return map;
367        }
368
369
370        public static List<SearchBundleEntryParts> getSearchBundleEntryParts(FhirContext theContext, IBaseBundle theBundle) {
371                RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
372                BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
373                List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
374
375                BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
376                BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl");
377                BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
378                BaseRuntimeChildDefinition searchChildDef = entryChildContentsDef.getChildByName("search");
379                BaseRuntimeElementCompositeDefinition<?> searchChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) searchChildDef.getChildByName("search");
380                BaseRuntimeChildDefinition searchModeChildDef = searchChildContentsDef.getChildByName("mode");
381
382                List<SearchBundleEntryParts> retVal = new ArrayList<>();
383                for (IBase nextEntry : entries) {
384                        SearchBundleEntryParts parts = getSearchBundleEntryParts(fullUrlChildDef, resourceChildDef, searchChildDef, searchModeChildDef, nextEntry);
385                        retVal.add(parts);
386                }
387                return retVal;
388
389        }
390
391        private static SearchBundleEntryParts getSearchBundleEntryParts( BaseRuntimeChildDefinition fullUrlChildDef, BaseRuntimeChildDefinition resourceChildDef, BaseRuntimeChildDefinition searchChildDef, BaseRuntimeChildDefinition searchModeChildDef, IBase entry) {
392                IBaseResource resource = null;
393                String matchMode = null;
394
395                String fullUrl = fullUrlChildDef
396                        .getAccessor()
397                        .getFirstValueOrNull(entry)
398                        .map(t->((IPrimitiveType<?>)t).getValueAsString())
399                        .orElse(null);
400
401                for (IBase nextResource : resourceChildDef.getAccessor().getValues(entry)) {
402                        resource = (IBaseResource) nextResource;
403                }
404
405                for (IBase nextSearch : searchChildDef.getAccessor().getValues(entry)) {
406                        for (IBase nextUrl : searchModeChildDef.getAccessor().getValues(nextSearch)) {
407                                matchMode = ((IPrimitiveType<?>) nextUrl).getValueAsString();
408                        }
409                }
410
411                SearchBundleEntryParts parts = new SearchBundleEntryParts(fullUrl, resource, matchMode);
412                return parts;
413        }
414
415        /**
416         * Given a bundle, and a consumer, apply the consumer to each entry in the bundle.
417         * @param theContext The FHIR Context
418         * @param theBundle The bundle to have its entries processed.
419         * @param theProcessor a {@link Consumer} which will operate on all the entries of a bundle.
420         */
421        public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
422                RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
423                BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
424                List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
425
426                BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
427                BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl");
428                BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
429                BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
430                BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
431                BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
432                BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
433                BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
434
435                for (IBase nextEntry : entries) {
436                        BundleEntryParts parts = getBundleEntryParts(fullUrlChildDef, resourceChildDef, requestChildDef, requestUrlChildDef, requestIfNoneExistChildDef, methodChildDef, nextEntry);
437                        /*
438                         * All 3 might be null - That's ok because we still want to know the
439                         * order in the original bundle.
440                         */
441                        BundleEntryMutator mutator = new BundleEntryMutator(theContext, nextEntry, requestChildDef, requestChildContentsDef, entryChildContentsDef);
442                        ModifiableBundleEntry entry = new ModifiableBundleEntry(parts, mutator);
443                        theProcessor.accept(entry);
444                }
445        }
446
447        private static BundleEntryParts getBundleEntryParts(BaseRuntimeChildDefinition fullUrlChildDef, BaseRuntimeChildDefinition resourceChildDef, BaseRuntimeChildDefinition requestChildDef, BaseRuntimeChildDefinition requestUrlChildDef, BaseRuntimeChildDefinition requestIfNoneExistChildDef, BaseRuntimeChildDefinition methodChildDef, IBase nextEntry) {
448                IBaseResource resource = null;
449                String url = null;
450                RequestTypeEnum requestType = null;
451                String conditionalUrl = null;
452                String fullUrl = fullUrlChildDef
453                        .getAccessor()
454                        .getFirstValueOrNull(nextEntry)
455                        .map(t->((IPrimitiveType<?>)t).getValueAsString())
456                        .orElse(null);
457
458                for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
459                        resource = (IBaseResource) nextResource;
460                }
461                for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
462                        for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
463                                url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
464                        }
465                        for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
466                                String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
467                                if (isNotBlank(methodString)) {
468                                        requestType = RequestTypeEnum.valueOf(methodString);
469                                }
470                        }
471
472                        if (requestType != null) {
473                                //noinspection EnumSwitchStatementWhichMissesCases
474                                switch (requestType) {
475                                        case PUT:
476                                                conditionalUrl = url != null && url.contains("?") ? url : null;
477                                                break;
478                                        case POST:
479                                                List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
480                                                if (ifNoneExistReps.size() > 0) {
481                                                        IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
482                                                        conditionalUrl = ifNoneExist.getValueAsString();
483                                                }
484                                                break;
485                                }
486                        }
487                }
488                BundleEntryParts parts = new BundleEntryParts(fullUrl, requestType, url, resource, conditionalUrl);
489                return parts;
490        }
491
492        /**
493         * Extract all of the resources from a given bundle
494         */
495        public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
496                return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class);
497        }
498
499        /**
500         * Extract all of ids of all the resources from a given bundle
501         */
502        public static List<String> toListOfResourceIds(FhirContext theContext, IBaseBundle theBundle) {
503                return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class).stream()
504                        .map(resource -> resource.getIdElement().getIdPart())
505                        .collect(Collectors.toList());
506        }
507
508        /**
509         * Extract all of the resources of a given type from a given bundle
510         */
511        @SuppressWarnings("unchecked")
512        public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) {
513                Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null");
514                List<T> retVal = new ArrayList<>();
515
516                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
517                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
518                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
519
520                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
521                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
522                for (IBase nextEntry : entries) {
523                        for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
524                                if (theTypeToInclude.isAssignableFrom(next.getClass())) {
525                                        retVal.add((T) next);
526                                }
527                        }
528                }
529                return retVal;
530        }
531
532        /**
533         * DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
534         * is a patch if the payload is a binary resource containing a patch. This method
535         * tests whether a resource (which should have come from
536         * <code>Bundle.entry.resource</code> is a Binary resource with a patch
537         * payload type.
538         */
539        public static boolean isDstu3TransactionPatch(FhirContext theContext, IBaseResource thePayloadResource) {
540                boolean isPatch = false;
541                if (thePayloadResource instanceof IBaseBinary) {
542                        String contentType = ((IBaseBinary) thePayloadResource).getContentType();
543                         try {
544                                 PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(theContext, contentType);
545                                 isPatch = true;
546                         } catch (InvalidRequestException e) {
547                                 // ignore
548                         }
549                }
550                return isPatch;
551        }
552
553
554        /**
555         * create a new bundle entry and set a value for a single field
556         * @param theContext     Context holding resource definition
557         * @param theFieldName   Child field name of the bundle entry to set
558         * @param theValues      The values to set on the bundle entry child field name
559         * @return the new bundle entry
560         */
561        public static IBase createNewBundleEntryWithSingleField(FhirContext theContext, String theFieldName, IBase... theValues) {
562                IBaseBundle newBundle = TerserUtil.newResource(theContext, "Bundle");
563                BaseRuntimeChildDefinition entryChildDef = theContext.getResourceDefinition(newBundle).getChildByName("entry");
564
565                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
566                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName(theFieldName);
567                IBase bundleEntry = entryChildElem.newInstance();
568                for (IBase value : theValues) {
569                        try {
570                                resourceChild.getMutator().addValue(bundleEntry, value);
571                        } catch (UnsupportedOperationException e) {
572                                ourLog.warn("Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only", bundleEntry, theValues);
573                                resourceChild.getMutator().setValue(bundleEntry, value);
574                                break;
575                        }
576                }
577                return bundleEntry;
578        }
579
580        private static class SortLegality {
581                private boolean myIsLegal;
582
583                SortLegality() {
584                        this.myIsLegal = true;
585                }
586                private void setLegal(boolean theLegal) {
587                        myIsLegal = theLegal;
588                }
589
590                public boolean isLegal() {
591                        return myIsLegal;
592                }
593        }
594
595}