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