001package ca.uhn.fhir.util; 002 003import ca.uhn.fhir.context.*; 004import ca.uhn.fhir.rest.api.PatchTypeEnum; 005import ca.uhn.fhir.rest.api.RequestTypeEnum; 006import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 007import ca.uhn.fhir.util.bundle.BundleEntryMutator; 008import ca.uhn.fhir.util.bundle.BundleEntryParts; 009import ca.uhn.fhir.util.bundle.EntryListAccumulator; 010import ca.uhn.fhir.util.bundle.ModifiableBundleEntry; 011import org.apache.commons.lang3.tuple.Pair; 012import org.hl7.fhir.instance.model.api.*; 013 014import java.util.ArrayList; 015import java.util.List; 016import java.util.Objects; 017import java.util.function.Consumer; 018 019import static org.apache.commons.lang3.StringUtils.isNotBlank; 020 021/* 022 * #%L 023 * HAPI FHIR - Core Library 024 * %% 025 * Copyright (C) 2014 - 2019 University Health Network 026 * %% 027 * Licensed under the Apache License, Version 2.0 (the "License"); 028 * you may not use this file except in compliance with the License. 029 * You may obtain a copy of the License at 030 * 031 * http://www.apache.org/licenses/LICENSE-2.0 032 * 033 * Unless required by applicable law or agreed to in writing, software 034 * distributed under the License is distributed on an "AS IS" BASIS, 035 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 036 * See the License for the specific language governing permissions and 037 * limitations under the License. 038 * #L% 039 */ 040 041/** 042 * Fetch resources from a bundle 043 */ 044public class BundleUtil { 045 046 /** 047 * @return Returns <code>null</code> if the link isn't found or has no value 048 */ 049 public static String getLinkUrlOfType(FhirContext theContext, IBaseBundle theBundle, String theLinkRelation) { 050 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 051 BaseRuntimeChildDefinition entryChild = def.getChildByName("link"); 052 List<IBase> links = entryChild.getAccessor().getValues(theBundle); 053 for (IBase nextLink : links) { 054 055 boolean isRightRel = false; 056 BaseRuntimeElementCompositeDefinition relDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass()); 057 BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation"); 058 List<IBase> relValues = relChild.getAccessor().getValues(nextLink); 059 for (IBase next : relValues) { 060 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) next; 061 if (theLinkRelation.equals(nextValue.getValueAsString())) { 062 isRightRel = true; 063 } 064 } 065 066 if (!isRightRel) { 067 continue; 068 } 069 070 BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(nextLink.getClass()); 071 BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url"); 072 List<IBase> values = urlChild.getAccessor().getValues(nextLink); 073 for (IBase nextUrl : values) { 074 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextUrl; 075 if (isNotBlank(nextValue.getValueAsString())) { 076 return nextValue.getValueAsString(); 077 } 078 } 079 080 } 081 082 return null; 083 } 084 085 @SuppressWarnings("unchecked") 086 public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) { 087 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 088 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 089 List<IBase> entries = entryChild.getAccessor().getValues(theBundle); 090 091 BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 092 BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); 093 094 BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request"); 095 BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request"); 096 097 BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url"); 098 099 List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size()); 100 for (IBase nextEntry : entries) { 101 102 String url = null; 103 IBaseResource resource = null; 104 105 for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) { 106 for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) { 107 url = ((IPrimitiveType<String>) nextUrlValue).getValue(); 108 } 109 } 110 111 // Should return 0..1 only 112 for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) { 113 resource = (IBaseResource) nextValue; 114 } 115 116 retVal.add(Pair.of(url, resource)); 117 } 118 119 return retVal; 120 } 121 122 public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) { 123 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 124 BaseRuntimeChildDefinition entryChild = def.getChildByName("type"); 125 List<IBase> entries = entryChild.getAccessor().getValues(theBundle); 126 if (entries.size() > 0) { 127 IPrimitiveType<?> typeElement = (IPrimitiveType<?>) entries.get(0); 128 return typeElement.getValueAsString(); 129 } 130 return null; 131 } 132 133 public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) { 134 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 135 BaseRuntimeChildDefinition entryChild = def.getChildByName("total"); 136 List<IBase> entries = entryChild.getAccessor().getValues(theBundle); 137 if (entries.size() > 0) { 138 IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0); 139 if (typeElement != null && typeElement.getValue() != null) { 140 return typeElement.getValue().intValue(); 141 } 142 } 143 return null; 144 } 145 146 public static void setTotal(FhirContext theContext, IBaseBundle theBundle, Integer theTotal) { 147 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 148 BaseRuntimeChildDefinition entryChild = def.getChildByName("total"); 149 IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) entryChild.getChildByName("total").newInstance(); 150 value.setValue(theTotal); 151 entryChild.getMutator().setValue(theBundle, value); 152 } 153 154 /** 155 * Extract all of the resources from a given bundle 156 */ 157 public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) { 158 EntryListAccumulator entryListAccumulator = new EntryListAccumulator(); 159 processEntries(theContext, theBundle, entryListAccumulator); 160 return entryListAccumulator.getList(); 161 } 162 163 164 public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) { 165 RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle); 166 BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry"); 167 List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle); 168 169 BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry"); 170 171 BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource"); 172 BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request"); 173 BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request"); 174 BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url"); 175 BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist"); 176 BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method"); 177 178 for (IBase nextEntry : entries) { 179 IBaseResource resource = null; 180 String url = null; 181 RequestTypeEnum requestType = null; 182 String conditionalUrl = null; 183 184 for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) { 185 resource = (IBaseResource) nextResource; 186 } 187 for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) { 188 for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) { 189 url = ((IPrimitiveType<?>) nextUrl).getValueAsString(); 190 } 191 for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) { 192 String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString(); 193 if (isNotBlank(methodString)) { 194 requestType = RequestTypeEnum.valueOf(methodString); 195 } 196 } 197 198 if (requestType != null) { 199 //noinspection EnumSwitchStatementWhichMissesCases 200 switch (requestType) { 201 case PUT: 202 conditionalUrl = url != null && url.contains("?") ? url : null; 203 break; 204 case POST: 205 List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest); 206 if (ifNoneExistReps.size() > 0) { 207 IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0); 208 conditionalUrl = ifNoneExist.getValueAsString(); 209 } 210 break; 211 } 212 } 213 } 214 215 /* 216 * All 3 might be null - That's ok because we still want to know the 217 * order in the original bundle. 218 */ 219 BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef); 220 ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(requestType, url, resource, conditionalUrl), mutator); 221 theProcessor.accept(entry); 222 } 223 } 224 225 /** 226 * Extract all of the resources from a given bundle 227 */ 228 public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) { 229 return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class); 230 } 231 232 /** 233 * Extract all of the resources of a given type from a given bundle 234 */ 235 @SuppressWarnings("unchecked") 236 public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) { 237 Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null"); 238 List<T> retVal = new ArrayList<>(); 239 240 RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); 241 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 242 List<IBase> entries = entryChild.getAccessor().getValues(theBundle); 243 244 BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 245 BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); 246 for (IBase nextEntry : entries) { 247 for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) { 248 if (theTypeToInclude.isAssignableFrom(next.getClass())) { 249 retVal.add((T) next); 250 } 251 } 252 } 253 return retVal; 254 } 255 256 /** 257 * DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle 258 * is a patch if the payload is a binary resource containing a patch. This method 259 * tests whether a resource (which should have come from 260 * <code>Bundle.entry.resource</code> is a Binary resource with a patch 261 * payload type. 262 */ 263 public static boolean isDstu3TransactionPatch(IBaseResource thePayloadResource) { 264 boolean isPatch = false; 265 if (thePayloadResource instanceof IBaseBinary) { 266 String contentType = ((IBaseBinary) thePayloadResource).getContentType(); 267 try { 268 PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType); 269 isPatch = true; 270 } catch (InvalidRequestException e) { 271 // ignore 272 } 273 } 274 return isPatch; 275 } 276}