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