001package ca.uhn.fhir.util;
002
003import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
004import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
005import ca.uhn.fhir.context.FhirContext;
006import ca.uhn.fhir.context.RuntimeResourceDefinition;
007import ca.uhn.fhir.rest.api.RequestTypeEnum;
008import org.apache.commons.lang3.tuple.Pair;
009import org.hl7.fhir.instance.model.api.IBase;
010import org.hl7.fhir.instance.model.api.IBaseBundle;
011import org.hl7.fhir.instance.model.api.IBaseResource;
012import org.hl7.fhir.instance.model.api.IPrimitiveType;
013
014import java.util.ArrayList;
015import java.util.List;
016import java.util.Objects;
017
018import static org.apache.commons.lang3.StringUtils.isNotBlank;
019
020/*
021 * #%L
022 * HAPI FHIR - Core Library
023 * %%
024 * Copyright (C) 2014 - 2019 University Health Network
025 * %%
026 * Licensed under the Apache License, Version 2.0 (the "License");
027 * you may not use this file except in compliance with the License.
028 * You may obtain a copy of the License at
029 *
030 *      http://www.apache.org/licenses/LICENSE-2.0
031 *
032 * Unless required by applicable law or agreed to in writing, software
033 * distributed under the License is distributed on an "AS IS" BASIS,
034 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
035 * See the License for the specific language governing permissions and
036 * limitations under the License.
037 * #L%
038 */
039
040/**
041 * Fetch resources from a bundle
042 */
043public class BundleUtil {
044
045        public static class BundleEntryParts {
046                private final RequestTypeEnum myRequestType;
047                private final IBaseResource myResource;
048                private final String myUrl;
049
050                BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource) {
051                        super();
052                        myRequestType = theRequestType;
053                        myUrl = theUrl;
054                        myResource = theResource;
055                }
056
057                public RequestTypeEnum getRequestType() {
058                        return myRequestType;
059                }
060
061                public IBaseResource getResource() {
062                        return myResource;
063                }
064
065                public String getUrl() {
066                        return myUrl;
067                }
068        }
069
070        /**
071         * @return Returns <code>null</code> if the link isn't found or has no value
072         */
073        public static String getLinkUrlOfType(FhirContext theContext, IBaseBundle theBundle, String theLinkRelation) {
074                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
075                BaseRuntimeChildDefinition entryChild = def.getChildByName("link");
076                List<IBase> links = entryChild.getAccessor().getValues(theBundle);
077                for (IBase nextLink : links) {
078
079                        boolean isRightRel = false;
080                        BaseRuntimeElementCompositeDefinition relDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
081                        BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation");
082                        List<IBase> relValues = relChild.getAccessor().getValues(nextLink);
083                        for (IBase next : relValues) {
084                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) next;
085                                if (theLinkRelation.equals(nextValue.getValueAsString())) {
086                                        isRightRel = true;
087                                }
088                        }
089
090                        if (!isRightRel) {
091                                continue;
092                        }
093
094                        BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass());
095                        BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url");
096                        List<IBase> values = urlChild.getAccessor().getValues(nextLink);
097                        for (IBase nextUrl : values) {
098                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextUrl;
099                                if (isNotBlank(nextValue.getValueAsString())) {
100                                        return nextValue.getValueAsString();
101                                }
102                        }
103
104                }
105
106                return null;
107        }
108
109        @SuppressWarnings("unchecked")
110        public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) {
111                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
112                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
113                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
114
115                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
116                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
117
118                BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
119                BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
120
121                BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
122
123                List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size());
124                for (IBase nextEntry : entries) {
125
126                        String url = null;
127                        IBaseResource resource = null;
128
129                        for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) {
130                                for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) {
131                                        url = ((IPrimitiveType<String>) nextUrlValue).getValue();
132                                }
133                        }
134
135                        // Should return 0..1 only
136                        for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) {
137                                resource = (IBaseResource) nextValue;
138                        }
139
140                        retVal.add(Pair.of(url, resource));
141                }
142
143                return retVal;
144        }
145
146        public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) {
147                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
148                BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
149                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
150                if (entries.size() > 0) {
151                        IPrimitiveType<?> typeElement = (IPrimitiveType<?>) entries.get(0);
152                        return typeElement.getValueAsString();
153                }
154                return null;
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                List<BundleEntryParts> retVal = new ArrayList<>();
183
184                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
185                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
186                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
187
188                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
189
190                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
191                BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
192                BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
193                BaseRuntimeChildDefinition urlChild = requestElem.getChildByName("url");
194                BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
195
196                for (IBase nextEntry : entries) {
197                        IBaseResource resource = null;
198                        String url = null;
199                        RequestTypeEnum requestType = null;
200
201                        for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
202                                resource = (IBaseResource) next;
203                        }
204                        for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
205                                for (IBase nextUrl : urlChild.getAccessor().getValues(nextRequest)) {
206                                        url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
207                                }
208                                for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
209                                        String methodString = ((IPrimitiveType<?>) nextUrl).getValueAsString();
210                                        if (isNotBlank(methodString)) {
211                                                requestType = RequestTypeEnum.valueOf(methodString);
212                                        }
213                                }
214                        }
215
216                        /*
217                         * All 3 might be null - That's ok because we still want to know the
218                         * order in the original bundle.
219                         */
220                        retVal.add(new BundleEntryParts(requestType, url, resource));
221                }
222
223
224                return retVal;
225        }
226
227        /**
228         * Extract all of the resources from a given bundle
229         */
230        public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
231                return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class);
232        }
233
234        /**
235         * Extract all of the resources of a given type from a given bundle
236         */
237        @SuppressWarnings("unchecked")
238        public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) {
239                Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null");
240                List<T> retVal = new ArrayList<>();
241
242                RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
243                BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
244                List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
245
246                BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
247                BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
248                for (IBase nextEntry : entries) {
249                        for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
250                                if (theTypeToInclude.isAssignableFrom(next.getClass())) {
251                                        retVal.add((T) next);
252                                }
253                        }
254                }
255                return retVal;
256        }
257}