001package ca.uhn.fhir.util;
002
003import ca.uhn.fhir.context.*;
004import ca.uhn.fhir.rest.api.PatchTypeEnum;
005import ca.uhn.fhir.rest.api.RequestTypeEnum;
006import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
007import ca.uhn.fhir.util.bundle.BundleEntryMutator;
008import ca.uhn.fhir.util.bundle.BundleEntryParts;
009import ca.uhn.fhir.util.bundle.EntryListAccumulator;
010import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
011import org.apache.commons.lang3.tuple.Pair;
012import org.hl7.fhir.instance.model.api.*;
013
014import java.util.ArrayList;
015import java.util.List;
016import java.util.Objects;
017import java.util.function.Consumer;
018
019import static org.apache.commons.lang3.StringUtils.isNotBlank;
020
021/*
022 * #%L
023 * HAPI FHIR - Core Library
024 * %%
025 * Copyright (C) 2014 - 2020 University Health Network
026 * %%
027 * Licensed under the Apache License, Version 2.0 (the "License");
028 * you may not use this file except in compliance with the License.
029 * You may obtain a copy of the License at
030 *
031 *      http://www.apache.org/licenses/LICENSE-2.0
032 *
033 * Unless required by applicable law or agreed to in writing, software
034 * distributed under the License is distributed on an "AS IS" BASIS,
035 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
036 * See the License for the specific language governing permissions and
037 * limitations under the License.
038 * #L%
039 */
040
041/**
042 * Fetch resources from a bundle
043 */
044public class BundleUtil {
045
046        /**
047         * @return Returns <code>null</code> if the link isn't found or has no value
048         */
049        public static String getLinkUrlOfType(FhirContext theContext, IBaseBundle theBundle, String theLinkRelation) {
050                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
051                BaseRuntimeChildDefinition entryChild = def.getChildByName("link");
052                List<IBase> links = entryChild.getAccessor().getValues(theBundle);
053                for (IBase nextLink : links) {
054
055                        boolean isRightRel = false;
056                        BaseRuntimeElementCompositeDefinition relDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
057                        BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation");
058                        List<IBase> relValues = relChild.getAccessor().getValues(nextLink);
059                        for (IBase next : relValues) {
060                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) next;
061                                if (theLinkRelation.equals(nextValue.getValueAsString())) {
062                                        isRightRel = true;
063                                }
064                        }
065
066                        if (!isRightRel) {
067                                continue;
068                        }
069
070                        BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
071                        BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url");
072                        List<IBase> values = urlChild.getAccessor().getValues(nextLink);
073                        for (IBase nextUrl : values) {
074                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextUrl;
075                                if (isNotBlank(nextValue.getValueAsString())) {
076                                        return nextValue.getValueAsString();
077                                }
078                        }
079
080                }
081
082                return null;
083        }
084
085        @SuppressWarnings("unchecked")
086        public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) {
087                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
088                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
089                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
090
091                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
092                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
093
094                BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
095                BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
096
097                BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
098
099                List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size());
100                for (IBase nextEntry : entries) {
101
102                        String url = null;
103                        IBaseResource resource = null;
104
105                        for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) {
106                                for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) {
107                                        url = ((IPrimitiveType<String>) nextUrlValue).getValue();
108                                }
109                        }
110
111                        // Should return 0..1 only
112                        for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) {
113                                resource = (IBaseResource) nextValue;
114                        }
115
116                        retVal.add(Pair.of(url, resource));
117                }
118
119                return retVal;
120        }
121
122        public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) {
123                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
124                BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
125                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
126                if (entries.size() > 0) {
127                        IPrimitiveType<?> typeElement = (IPrimitiveType<?>) entries.get(0);
128                        return typeElement.getValueAsString();
129                }
130                return null;
131        }
132
133        public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) {
134                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
135                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
136                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
137                if (entries.size() > 0) {
138                        IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0);
139                        if (typeElement != null && typeElement.getValue() != null) {
140                                return typeElement.getValue().intValue();
141                        }
142                }
143                return null;
144        }
145
146        public static void setTotal(FhirContext theContext, IBaseBundle theBundle, Integer theTotal) {
147                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
148                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
149                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) entryChild.getChildByName("total").newInstance();
150                value.setValue(theTotal);
151                entryChild.getMutator().setValue(theBundle, value);
152        }
153
154        /**
155         * Extract all of the resources from a given bundle
156         */
157        public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
158                EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
159                processEntries(theContext, theBundle, entryListAccumulator);
160                return entryListAccumulator.getList();
161        }
162
163
164        public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
165                RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
166                BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
167                List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
168
169                BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
170
171                BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl");
172
173                BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
174                BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
175                BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
176                BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
177                BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
178                BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
179
180                for (IBase nextEntry : entries) {
181                        IBaseResource resource = null;
182                        String url = null;
183                        RequestTypeEnum requestType = null;
184                        String conditionalUrl = null;
185                        String fullUrl = fullUrlChildDef
186                                .getAccessor()
187                                .getFirstValueOrNull(nextEntry)
188                                .map(t->((IPrimitiveType<?>)t).getValueAsString())
189                                .orElse(null);
190
191                        for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
192                                resource = (IBaseResource) nextResource;
193                        }
194                        for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
195                                for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
196                                        url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
197                                }
198                                for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
199                                        String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
200                                        if (isNotBlank(methodString)) {
201                                                requestType = RequestTypeEnum.valueOf(methodString);
202                                        }
203                                }
204
205                                if (requestType != null) {
206                                        //noinspection EnumSwitchStatementWhichMissesCases
207                                        switch (requestType) {
208                                                case PUT:
209                                                        conditionalUrl = url != null && url.contains("?") ? url : null;
210                                                        break;
211                                                case POST:
212                                                        List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
213                                                        if (ifNoneExistReps.size() > 0) {
214                                                                IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
215                                                                conditionalUrl = ifNoneExist.getValueAsString();
216                                                        }
217                                                        break;
218                                        }
219                                }
220                        }
221
222                        /*
223                         * All 3 might be null - That's ok because we still want to know the
224                         * order in the original bundle.
225                         */
226                        BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef);
227                        ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(fullUrl, requestType, url, resource, conditionalUrl), mutator);
228                        theProcessor.accept(entry);
229                }
230        }
231
232        /**
233         * Extract all of the resources from a given bundle
234         */
235        public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
236                return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class);
237        }
238
239        /**
240         * Extract all of the resources of a given type from a given bundle
241         */
242        @SuppressWarnings("unchecked")
243        public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) {
244                Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null");
245                List<T> retVal = new ArrayList<>();
246
247                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
248                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
249                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
250
251                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
252                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
253                for (IBase nextEntry : entries) {
254                        for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
255                                if (theTypeToInclude.isAssignableFrom(next.getClass())) {
256                                        retVal.add((T) next);
257                                }
258                        }
259                }
260                return retVal;
261        }
262
263        /**
264         * DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
265         * is a patch if the payload is a binary resource containing a patch. This method
266         * tests whether a resource (which should have come from
267         * <code>Bundle.entry.resource</code> is a Binary resource with a patch
268         * payload type.
269         */
270        public static boolean isDstu3TransactionPatch(IBaseResource thePayloadResource) {
271                boolean isPatch = false;
272                if (thePayloadResource instanceof IBaseBinary) {
273                        String contentType = ((IBaseBinary) thePayloadResource).getContentType();
274                         try {
275                                 PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
276                                 isPatch = true;
277                         } catch (InvalidRequestException e) {
278                                 // ignore
279                         }
280                }
281                return isPatch;
282        }
283}