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}