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