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