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