001package org.hl7.fhir.dstu2016may.hapi.rest.server; 002 003/* 004 * #%L 005 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) 006 * %% 007 * Copyright (C) 2014 - 2015 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.dstu2016may.model.*; 027import org.hl7.fhir.dstu2016may.model.Bundle.*; 028import org.hl7.fhir.instance.model.api.*; 029 030import ca.uhn.fhir.context.FhirContext; 031import ca.uhn.fhir.context.api.BundleInclusionRule; 032import ca.uhn.fhir.model.api.Include; 033import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 034import ca.uhn.fhir.model.valueset.BundleTypeEnum; 035import ca.uhn.fhir.rest.api.Constants; 036import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; 037import ca.uhn.fhir.util.ResourceReferenceInfo; 038 039public class Dstu2_1BundleFactory implements IVersionSpecificBundleFactory { 040 041 private String myBase; 042 private Bundle myBundle; 043 private FhirContext myContext; 044 045 public Dstu2_1BundleFactory(FhirContext theContext) { 046 myContext = theContext; 047 } 048 049 private void addResourcesForSearch(List<? extends IBaseResource> theResult) { 050 List<IBaseResource> includedResources = new ArrayList<IBaseResource>(); 051 Set<IIdType> addedResourceIds = new HashSet<IIdType>(); 052 053 for (IBaseResource next : theResult) { 054 if (next.getIdElement().isEmpty() == false) { 055 addedResourceIds.add(next.getIdElement()); 056 } 057 } 058 059 for (IBaseResource nextBaseRes : theResult) { 060 Resource next = (Resource) nextBaseRes; 061 Set<String> containedIds = new HashSet<String>(); 062 if (next instanceof DomainResource) { 063 for (Resource nextContained : ((DomainResource) next).getContained()) { 064 if (nextContained.getIdElement().isEmpty() == false) { 065 containedIds.add(nextContained.getIdElement().getValue()); 066 } 067 } 068 } 069 070 List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class); 071 do { 072 List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>(); 073 074 for (IBaseReference nextRef : references) { 075 IAnyResource nextRes = (IAnyResource) nextRef.getResource(); 076 if (nextRes != null) { 077 if (nextRes.getIdElement().hasIdPart()) { 078 if (containedIds.contains(nextRes.getIdElement().getValue())) { 079 // Don't add contained IDs as top level resources 080 continue; 081 } 082 083 IIdType id = nextRes.getIdElement(); 084 if (id.hasResourceType() == false) { 085 String resName = myContext.getResourceDefinition(nextRes).getName(); 086 id = id.withResourceType(resName); 087 } 088 089 if (!addedResourceIds.contains(id)) { 090 addedResourceIds.add(id); 091 addedResourcesThisPass.add(nextRes); 092 } 093 094 } 095 } 096 } 097 098 // Linked resources may themselves have linked resources 099 references = new ArrayList<IBaseReference>(); 100 for (IAnyResource iResource : addedResourcesThisPass) { 101 List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, IBaseReference.class); 102 references.addAll(newReferences); 103 } 104 105 includedResources.addAll(addedResourcesThisPass); 106 107 } while (references.isEmpty() == false); 108 109 BundleEntryComponent entry = myBundle.addEntry().setResource(next); 110 if (next.getIdElement().hasBaseUrl()) { 111 entry.setFullUrl(next.getId()); 112 } 113 114 String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next); 115 if (httpVerb != null) { 116 entry.getRequest().getMethodElement().setValueAsString(httpVerb); 117 entry.getRequest().getUrlElement().setValue(next.getId()); 118 } 119 } 120 121 /* 122 * Actually add the resources to the bundle 123 */ 124 for (IBaseResource next : includedResources) { 125 BundleEntryComponent entry = myBundle.addEntry(); 126 entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.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<IAnyResource> includedResources = new ArrayList<IAnyResource>(); 138 Set<IIdType> addedResourceIds = new HashSet<IIdType>(); 139 140 for (IBaseResource next : theResult) { 141 if (next.getIdElement().isEmpty() == false) { 142 addedResourceIds.add(next.getIdElement()); 143 } 144 } 145 146 for (IBaseResource next : theResult) { 147 148 Set<String> containedIds = new HashSet<String>(); 149 150 if (next instanceof DomainResource) { 151 for (Resource nextContained : ((DomainResource) next).getContained()) { 152 if (isNotBlank(nextContained.getId())) { 153 containedIds.add(nextContained.getId()); 154 } 155 } 156 } 157 158 List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next); 159 do { 160 List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>(); 161 162 for (ResourceReferenceInfo nextRefInfo : references) { 163 if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes)) { 164 continue; 165 } 166 167 IAnyResource nextRes = (IAnyResource) nextRefInfo.getResourceReference().getResource(); 168 if (nextRes != null) { 169 if (nextRes.getIdElement().hasIdPart()) { 170 if (containedIds.contains(nextRes.getIdElement().getValue())) { 171 // Don't add contained IDs as top level resources 172 continue; 173 } 174 175 IIdType id = nextRes.getIdElement(); 176 if (id.hasResourceType() == false) { 177 String resName = myContext.getResourceDefinition(nextRes).getName(); 178 id = id.withResourceType(resName); 179 } 180 181 if (!addedResourceIds.contains(id)) { 182 addedResourceIds.add(id); 183 addedResourcesThisPass.add(nextRes); 184 } 185 186 } 187 } 188 } 189 190 includedResources.addAll(addedResourcesThisPass); 191 192 // Linked resources may themselves have linked resources 193 references = new ArrayList<ResourceReferenceInfo>(); 194 for (IAnyResource iResource : addedResourcesThisPass) { 195 List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource); 196 references.addAll(newReferences); 197 } 198 } while (references.isEmpty() == false); 199 200 BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next); 201 Resource nextAsResource = (Resource) next; 202 IIdType id = populateBundleEntryFullUrl(next, entry); 203 String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextAsResource); 204 if (httpVerb != null) { 205 entry.getRequest().getMethodElement().setValueAsString(httpVerb); 206 if (id != null) { 207 entry.getRequest().setUrl(id.getValue()); 208 } 209 } 210 211 String searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextAsResource); 212 if (searchMode != null) { 213 entry.getSearch().getModeElement().setValueAsString(searchMode); 214 } 215 } 216 217 /* 218 * Actually add the resources to the bundle 219 */ 220 for (IAnyResource next : includedResources) { 221 BundleEntryComponent entry = myBundle.addEntry(); 222 entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE); 223 populateBundleEntryFullUrl(next, entry); 224 } 225 226 } 227 228 @Override 229 public void addRootPropertiesToBundle(String theId, String theServerBase, String theLinkSelf, String theLinkPrev, String theLinkNext, Integer theTotalResults, BundleTypeEnum theBundleType, 230 IPrimitiveType<Date> theLastUpdated) { 231 ensureBundle(); 232 233 myBase = theServerBase; 234 235 if (myBundle.getIdElement().isEmpty()) { 236 myBundle.setId(theId); 237 } 238 if (myBundle.getIdElement().isEmpty()) { 239 myBundle.setId(UUID.randomUUID().toString()); 240 } 241 242 if (myBundle.getMeta().getLastUpdated() == null && theLastUpdated != null) { 243 myBundle.getMeta().getLastUpdatedElement().setValueAsString(theLastUpdated.getValueAsString()); 244 } 245 246 if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theLinkSelf)) { 247 myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theLinkSelf); 248 } 249 if (!hasLink(Constants.LINK_NEXT, myBundle) && isNotBlank(theLinkNext)) { 250 myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(theLinkNext); 251 } 252 if (!hasLink(Constants.LINK_PREVIOUS, myBundle) && isNotBlank(theLinkPrev)) { 253 myBundle.addLink().setRelation(Constants.LINK_PREVIOUS).setUrl(theLinkPrev); 254 } 255 256 if (myBundle.getTypeElement().isEmpty() && theBundleType != null) { 257 myBundle.getTypeElement().setValueAsString(theBundleType.getCode()); 258 } 259 260 if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) { 261 myBundle.getTotalElement().setValue(theTotalResults); 262 } 263 } 264 265 private void ensureBundle() { 266 if (myBundle == null) { 267 myBundle = new Bundle(); 268 } 269 } 270 271 @Override 272 public IBaseResource getResourceBundle() { 273 return myBundle; 274 } 275 276 private boolean hasLink(String theLinkType, Bundle theBundle) { 277 for (BundleLinkComponent next : theBundle.getLink()) { 278 if (theLinkType.equals(next.getRelation())) { 279 return true; 280 } 281 } 282 return false; 283 } 284 285 @Override 286 public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults, 287 BundleTypeEnum theBundleType) { 288 ensureBundle(); 289 290 myBundle.setId(UUID.randomUUID().toString()); 291 292 myBundle.getMeta().setLastUpdated(new Date()); 293 294 myBundle.addLink().setRelation(Constants.LINK_FHIR_BASE).setUrl(theServerBase); 295 myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theCompleteUrl); 296 myBundle.getTypeElement().setValueAsString(theBundleType.getCode()); 297 298 if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) { 299 for (IBaseResource nextBaseRes : theResources) { 300 Resource next = (Resource) nextBaseRes; 301 BundleEntryComponent nextEntry = myBundle.addEntry(); 302 303 nextEntry.setResource(next); 304 if (next.getIdElement().isEmpty()) { 305 nextEntry.getRequest().setMethod(HTTPVerb.POST); 306 } else { 307 nextEntry.getRequest().setMethod(HTTPVerb.PUT); 308 if (next.getIdElement().isAbsolute()) { 309 nextEntry.getRequest().setUrl(next.getId()); 310 } else { 311 String resourceType = myContext.getResourceDefinition(next).getName(); 312 nextEntry.getRequest().setUrl(new IdType(theServerBase, resourceType, next.getIdElement().getIdPart(), next.getIdElement().getVersionIdPart()).getValue()); 313 } 314 } 315 } 316 } else { 317 addResourcesForSearch(theResources); 318 } 319 320 myBundle.getTotalElement().setValue(theTotalResults); 321 } 322 323 @Override 324 public void initializeWithBundleResource(IBaseResource theBundle) { 325 myBundle = (Bundle) theBundle; 326 } 327 328 private IIdType populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) { 329 IIdType idElement = null; 330 if (next.getIdElement().hasBaseUrl()) { 331 idElement = next.getIdElement(); 332 entry.setFullUrl(idElement.toVersionless().getValue()); 333 } else { 334 if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { 335 idElement = next.getIdElement(); 336 idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); 337 entry.setFullUrl(idElement.toVersionless().getValue()); 338 } 339 } 340 return idElement; 341 } 342 343 @Override 344 public List<IBaseResource> toListOfResources() { 345 ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>(); 346 for (BundleEntryComponent next : myBundle.getEntry()) { 347 if (next.getResource() != null) { 348 retVal.add(next.getResource()); 349 } else if (next.getResponse().getLocationElement().isEmpty() == false) { 350 IdType id = new IdType(next.getResponse().getLocation()); 351 String resourceType = id.getResourceType(); 352 if (isNotBlank(resourceType)) { 353 IAnyResource res = (IAnyResource) myContext.getResourceDefinition(resourceType).newInstance(); 354 res.setId(id); 355 retVal.add(res); 356 } 357 } 358 } 359 return retVal; 360 } 361 362}