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