001package ca.uhn.fhir.rest.server.provider.dstu2; 002 003/* 004 * #%L 005 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) 006 * %% 007 * Copyright (C) 2014 - 2018 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022import static org.apache.commons.lang3.StringUtils.isNotBlank; 023 024import java.util.*; 025 026import org.hl7.fhir.instance.model.api.*; 027 028import ca.uhn.fhir.context.FhirContext; 029import ca.uhn.fhir.context.api.BundleInclusionRule; 030import ca.uhn.fhir.model.api.*; 031import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; 032import ca.uhn.fhir.model.dstu2.resource.Bundle; 033import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; 034import ca.uhn.fhir.model.dstu2.resource.Bundle.Link; 035import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; 036import ca.uhn.fhir.model.dstu2.valueset.SearchEntryModeEnum; 037import ca.uhn.fhir.model.primitive.IdDt; 038import ca.uhn.fhir.model.primitive.InstantDt; 039import ca.uhn.fhir.model.valueset.*; 040import ca.uhn.fhir.rest.api.Constants; 041import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; 042import ca.uhn.fhir.util.ResourceReferenceInfo; 043 044public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { 045 private String myBase; 046 private Bundle myBundle; 047 private FhirContext myContext; 048 049 public Dstu2BundleFactory(FhirContext theContext) { 050 myContext = theContext; 051 } 052 053 private void addResourcesForSearch(List<? extends IBaseResource> theResult) { 054 List<IBaseResource> includedResources = new ArrayList<IBaseResource>(); 055 Set<IIdType> addedResourceIds = new HashSet<IIdType>(); 056 057 for (IBaseResource next : theResult) { 058 if (next.getIdElement().isEmpty() == false) { 059 addedResourceIds.add(next.getIdElement()); 060 } 061 } 062 063 for (IBaseResource nextBaseRes : theResult) { 064 IResource next = (IResource) nextBaseRes; 065 Set<String> containedIds = new HashSet<String>(); 066 for (IResource nextContained : next.getContained().getContainedResources()) { 067 if (nextContained.getId().isEmpty() == false) { 068 containedIds.add(nextContained.getId().getValue()); 069 } 070 } 071 072 List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class); 073 do { 074 List<IResource> addedResourcesThisPass = new ArrayList<IResource>(); 075 076 for (BaseResourceReferenceDt nextRef : references) { 077 IResource nextRes = (IResource) nextRef.getResource(); 078 if (nextRes != null) { 079 if (nextRes.getId().hasIdPart()) { 080 if (containedIds.contains(nextRes.getId().getValue())) { 081 // Don't add contained IDs as top level resources 082 continue; 083 } 084 085 IdDt id = nextRes.getId(); 086 if (id.hasResourceType() == false) { 087 String resName = myContext.getResourceDefinition(nextRes).getName(); 088 id = id.withResourceType(resName); 089 } 090 091 if (!addedResourceIds.contains(id)) { 092 addedResourceIds.add(id); 093 addedResourcesThisPass.add(nextRes); 094 } 095 096 } 097 } 098 } 099 100 // Linked resources may themselves have linked resources 101 references = new ArrayList<BaseResourceReferenceDt>(); 102 for (IResource iResource : addedResourcesThisPass) { 103 List<BaseResourceReferenceDt> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class); 104 references.addAll(newReferences); 105 } 106 107 includedResources.addAll(addedResourcesThisPass); 108 109 } while (references.isEmpty() == false); 110 111 Entry entry = myBundle.addEntry().setResource(next); 112 if (next.getId().hasBaseUrl()) { 113 entry.setFullUrl(next.getId().getValue()); 114 } 115 BundleEntryTransactionMethodEnum httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next); 116 if (httpVerb != null) { 117 entry.getRequest().getMethodElement().setValueAsString(httpVerb.getCode()); 118 } 119 } 120 121 /* 122 * Actually add the resources to the bundle 123 */ 124 for (IBaseResource next : includedResources) { 125 Entry entry = myBundle.addEntry(); 126 entry.setResource((IResource) next).getSearch().setMode(SearchEntryModeEnum.INCLUDE); 127 if (next.getIdElement().hasBaseUrl()) { 128 entry.setFullUrl(next.getIdElement().getValue()); 129 } 130 } 131 } 132 133 @Override 134 public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) { 135 ensureBundle(); 136 137 List<IResource> includedResources = new ArrayList<IResource>(); 138 Set<IdDt> addedResourceIds = new HashSet<IdDt>(); 139 140 for (IBaseResource next : theResult) { 141 if (next.getIdElement().isEmpty() == false) { 142 addedResourceIds.add((IdDt) next.getIdElement()); 143 } 144 } 145 146 for (IBaseResource nextBaseRes : theResult) { 147 IResource next = (IResource) nextBaseRes; 148 149 Set<String> containedIds = new HashSet<String>(); 150 for (IResource nextContained : next.getContained().getContainedResources()) { 151 if (nextContained.getId().isEmpty() == false) { 152 containedIds.add(nextContained.getId().getValue()); 153 } 154 } 155 156 List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next); 157 do { 158 List<IResource> addedResourcesThisPass = new ArrayList<IResource>(); 159 160 for (ResourceReferenceInfo nextRefInfo : references) { 161 if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes)) 162 continue; 163 164 IResource nextRes = (IResource) nextRefInfo.getResourceReference().getResource(); 165 if (nextRes != null) { 166 if (nextRes.getId().hasIdPart()) { 167 if (containedIds.contains(nextRes.getId().getValue())) { 168 // Don't add contained IDs as top level resources 169 continue; 170 } 171 172 IdDt id = nextRes.getId(); 173 if (id.hasResourceType() == false) { 174 String resName = myContext.getResourceDefinition(nextRes).getName(); 175 id = id.withResourceType(resName); 176 } 177 178 if (!addedResourceIds.contains(id)) { 179 addedResourceIds.add(id); 180 addedResourcesThisPass.add(nextRes); 181 } 182 183 } 184 } 185 } 186 187 includedResources.addAll(addedResourcesThisPass); 188 189 // Linked resources may themselves have linked resources 190 references = new ArrayList<ResourceReferenceInfo>(); 191 for (IResource iResource : addedResourcesThisPass) { 192 List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource); 193 references.addAll(newReferences); 194 } 195 } while (references.isEmpty() == false); 196 197 Entry entry = myBundle.addEntry().setResource(next); 198 BundleEntryTransactionMethodEnum httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next); 199 if (httpVerb != null) { 200 entry.getRequest().getMethodElement().setValueAsString(httpVerb.getCode()); 201 } 202 populateBundleEntryFullUrl(next, entry); 203 204 BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next); 205 if (searchMode != null) { 206 entry.getSearch().getModeElement().setValue(searchMode.getCode()); 207 } 208 } 209 210 /* 211 * Actually add the resources to the bundle 212 */ 213 for (IResource next : includedResources) { 214 Entry entry = myBundle.addEntry(); 215 entry.setResource(next).getSearch().setMode(SearchEntryModeEnum.INCLUDE); 216 populateBundleEntryFullUrl(next, entry); 217 } 218 219 } 220 221 @Override 222 public void addRootPropertiesToBundle(String theId, String theServerBase, String theLinkSelf, String theLinkPrev, String theLinkNext, Integer theTotalResults, BundleTypeEnum theBundleType, 223 IPrimitiveType<Date> theLastUpdated) { 224 ensureBundle(); 225 226 myBase = theServerBase; 227 228 if (myBundle.getIdElement().isEmpty()) { 229 myBundle.setId(theId); 230 } 231 if (myBundle.getId().isEmpty()) { 232 myBundle.setId(UUID.randomUUID().toString()); 233 } 234 235 if (ResourceMetadataKeyEnum.UPDATED.get(myBundle) == null) { 236 ResourceMetadataKeyEnum.UPDATED.put(myBundle, (InstantDt) theLastUpdated); 237 } 238 239 if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theLinkSelf)) { 240 myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theLinkSelf); 241 } 242 if (!hasLink(Constants.LINK_NEXT, myBundle) && isNotBlank(theLinkNext)) { 243 myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(theLinkNext); 244 } 245 if (!hasLink(Constants.LINK_PREVIOUS, myBundle) && isNotBlank(theLinkPrev)) { 246 myBundle.addLink().setRelation(Constants.LINK_PREVIOUS).setUrl(theLinkPrev); 247 } 248 249 if (myBundle.getTypeElement().isEmpty() && theBundleType != null) { 250 myBundle.getTypeElement().setValueAsString(theBundleType.getCode()); 251 } 252 253 if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) { 254 myBundle.getTotalElement().setValue(theTotalResults); 255 } 256 } 257 258 private void ensureBundle() { 259 if (myBundle == null) { 260 myBundle = new Bundle(); 261 } 262 } 263 264 @Override 265 public IResource getResourceBundle() { 266 return myBundle; 267 } 268 269 private boolean hasLink(String theLinkType, Bundle theBundle) { 270 for (Link next : theBundle.getLink()) { 271 if (theLinkType.equals(next.getRelation())) { 272 return true; 273 } 274 } 275 return false; 276 } 277 278 @Override 279 public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults, 280 BundleTypeEnum theBundleType) { 281 ensureBundle(); 282 283 myBundle.setId(UUID.randomUUID().toString()); 284 285 ResourceMetadataKeyEnum.PUBLISHED.put(myBundle, InstantDt.withCurrentTime()); 286 287 myBundle.addLink().setRelation(Constants.LINK_FHIR_BASE).setUrl(theServerBase); 288 myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theCompleteUrl); 289 myBundle.getTypeElement().setValueAsString(theBundleType.getCode()); 290 291 if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) { 292 for (IBaseResource nextBaseRes : theResources) { 293 IResource next = (IResource) nextBaseRes; 294 Entry nextEntry = myBundle.addEntry(); 295 296 nextEntry.setResource(next); 297 if (next.getId().isEmpty()) { 298 nextEntry.getRequest().setMethod(HTTPVerbEnum.POST); 299 } else { 300 nextEntry.getRequest().setMethod(HTTPVerbEnum.PUT); 301 if (next.getId().isAbsolute()) { 302 nextEntry.getRequest().setUrl(next.getId()); 303 } else { 304 String resourceType = myContext.getResourceDefinition(next).getName(); 305 nextEntry.getRequest().setUrl(new IdDt(theServerBase, resourceType, next.getId().getIdPart(), next.getId().getVersionIdPart()).getValue()); 306 } 307 } 308 } 309 } else { 310 addResourcesForSearch(theResources); 311 } 312 313 myBundle.getTotalElement().setValue(theTotalResults); 314 } 315 316 @Override 317 public void initializeWithBundleResource(IBaseResource theBundle) { 318 myBundle = (Bundle) theBundle; 319 } 320 321 private void populateBundleEntryFullUrl(IResource next, Entry entry) { 322 if (next.getId().hasBaseUrl()) { 323 entry.setFullUrl(next.getId().toVersionless().getValue()); 324 } else { 325 if (isNotBlank(myBase) && next.getId().hasIdPart()) { 326 IdDt id = next.getId().toVersionless(); 327 id = id.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); 328 entry.setFullUrl(id.getValue()); 329 } 330 } 331 } 332 333 @Override 334 public List<IBaseResource> toListOfResources() { 335 ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>(); 336 for (Entry next : myBundle.getEntry()) { 337 if (next.getResource() != null) { 338 retVal.add(next.getResource()); 339 } else if (next.getResponse().getLocationElement().isEmpty() == false) { 340 IdDt id = new IdDt(next.getResponse().getLocation()); 341 String resourceType = id.getResourceType(); 342 if (isNotBlank(resourceType)) { 343 IResource res = (IResource) myContext.getResourceDefinition(resourceType).newInstance(); 344 res.setId(id); 345 retVal.add(res); 346 } 347 } 348 } 349 return retVal; 350 } 351 352}