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 - 2021 Smile CDR, Inc.
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 void setBundleType(FhirContext theContext, IBaseBundle theBundle, String theType) {
134                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
135                BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
136                BaseRuntimeElementDefinition<?> element = entryChild.getChildByName("type");
137                IPrimitiveType<?> typeInstance = (IPrimitiveType<?>) element.newInstance(entryChild.getInstanceConstructorArguments());
138                typeInstance.setValueAsString(theType);
139
140                entryChild.getMutator().setValue(theBundle, typeInstance);
141        }
142
143        public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) {
144                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
145                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
146                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
147                if (entries.size() > 0) {
148                        IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0);
149                        if (typeElement != null && typeElement.getValue() != null) {
150                                return typeElement.getValue().intValue();
151                        }
152                }
153                return null;
154        }
155
156        public static void setTotal(FhirContext theContext, IBaseBundle theBundle, Integer theTotal) {
157                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
158                BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
159                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) entryChild.getChildByName("total").newInstance();
160                value.setValue(theTotal);
161                entryChild.getMutator().setValue(theBundle, value);
162        }
163
164        /**
165         * Extract all of the resources from a given bundle
166         */
167        public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
168                EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
169                processEntries(theContext, theBundle, entryListAccumulator);
170                return entryListAccumulator.getList();
171        }
172
173
174        public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
175                RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
176                BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
177                List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
178
179                BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
180
181                BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl");
182
183                BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
184                BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
185                BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
186                BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
187                BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
188                BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
189
190                for (IBase nextEntry : entries) {
191                        IBaseResource resource = null;
192                        String url = null;
193                        RequestTypeEnum requestType = null;
194                        String conditionalUrl = null;
195                        String fullUrl = fullUrlChildDef
196                                .getAccessor()
197                                .getFirstValueOrNull(nextEntry)
198                                .map(t->((IPrimitiveType<?>)t).getValueAsString())
199                                .orElse(null);
200
201                        for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
202                                resource = (IBaseResource) nextResource;
203                        }
204                        for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
205                                for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
206                                        url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
207                                }
208                                for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
209                                        String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
210                                        if (isNotBlank(methodString)) {
211                                                requestType = RequestTypeEnum.valueOf(methodString);
212                                        }
213                                }
214
215                                if (requestType != null) {
216                                        //noinspection EnumSwitchStatementWhichMissesCases
217                                        switch (requestType) {
218                                                case PUT:
219                                                        conditionalUrl = url != null && url.contains("?") ? url : null;
220                                                        break;
221                                                case POST:
222                                                        List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
223                                                        if (ifNoneExistReps.size() > 0) {
224                                                                IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
225                                                                conditionalUrl = ifNoneExist.getValueAsString();
226                                                        }
227                                                        break;
228                                        }
229                                }
230                        }
231
232                        /*
233                         * All 3 might be null - That's ok because we still want to know the
234                         * order in the original bundle.
235                         */
236                        BundleEntryMutator mutator = new BundleEntryMutator(theContext, nextEntry, requestChildDef, requestChildContentsDef, entryChildContentsDef);
237                        ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(fullUrl, requestType, url, resource, conditionalUrl), mutator);
238                        theProcessor.accept(entry);
239                }
240        }
241
242        /**
243         * Extract all of the resources from a given bundle
244         */
245        public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
246                return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class);
247        }
248
249        /**
250         * Extract all of the resources of a given type from a given bundle
251         */
252        @SuppressWarnings("unchecked")
253        public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) {
254                Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null");
255                List<T> retVal = new ArrayList<>();
256
257                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
258                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
259                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
260
261                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
262                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
263                for (IBase nextEntry : entries) {
264                        for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
265                                if (theTypeToInclude.isAssignableFrom(next.getClass())) {
266                                        retVal.add((T) next);
267                                }
268                        }
269                }
270                return retVal;
271        }
272
273        /**
274         * DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
275         * is a patch if the payload is a binary resource containing a patch. This method
276         * tests whether a resource (which should have come from
277         * <code>Bundle.entry.resource</code> is a Binary resource with a patch
278         * payload type.
279         */
280        public static boolean isDstu3TransactionPatch(FhirContext theContext, IBaseResource thePayloadResource) {
281                boolean isPatch = false;
282                if (thePayloadResource instanceof IBaseBinary) {
283                        String contentType = ((IBaseBinary) thePayloadResource).getContentType();
284                         try {
285                                 PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(theContext, contentType);
286                                 isPatch = true;
287                         } catch (InvalidRequestException e) {
288                                 // ignore
289                         }
290                }
291                return isPatch;
292        }
293}