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