001package ca.uhn.fhir.model.api; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 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.ArrayList; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.commons.lang3.StringUtils; 030import org.apache.commons.lang3.Validate; 031import org.apache.commons.lang3.builder.ToStringBuilder; 032import org.apache.commons.lang3.builder.ToStringStyle; 033 034import ca.uhn.fhir.context.FhirContext; 035import ca.uhn.fhir.context.RuntimeResourceDefinition; 036import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; 037import ca.uhn.fhir.model.primitive.BoundCodeDt; 038import ca.uhn.fhir.model.primitive.DecimalDt; 039import ca.uhn.fhir.model.primitive.IdDt; 040import ca.uhn.fhir.model.primitive.InstantDt; 041import ca.uhn.fhir.model.primitive.IntegerDt; 042import ca.uhn.fhir.model.primitive.StringDt; 043import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 044import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; 045import ca.uhn.fhir.model.valueset.BundleTypeEnum; 046import ca.uhn.fhir.rest.server.Constants; 047import ca.uhn.fhir.util.UrlUtil; 048 049public class Bundle extends BaseBundle /* implements IBase implements IElement */{ 050 051 private static final long serialVersionUID = 5811989173275366745L; 052 private ResourceMetadataMap myResourceMetadata; 053 private BoundCodeDt<BundleTypeEnum> myType; 054 private StringDt myBundleId; 055 private TagList myCategories; 056 private List<BundleEntry> myEntries; 057 private volatile transient Map<IdDt, IResource> myIdToEntries; 058 private StringDt myLinkBase; 059 private StringDt myLinkFirst; 060 private StringDt myLinkLast; 061 private StringDt myLinkNext; 062 private StringDt myLinkPrevious; 063 private StringDt myLinkSelf; 064 private StringDt myTitle; 065 private IntegerDt myTotalResults; 066 067 /** 068 * @deprecated Tags wil become immutable in a future release of HAPI, so 069 * {@link #addCategory(String, String, String)} should be used instead 070 */ 071 @Deprecated 072 public Tag addCategory() { 073 Tag retVal = new Tag(); 074 getCategories().add(retVal); 075 return retVal; 076 } 077 078 public void addCategory(String theScheme, String theTerm, String theLabel) { 079 getCategories().add(new Tag(theScheme, theTerm, theLabel)); 080 } 081 082 public void addCategory(Tag theTag) { 083 getCategories().add(theTag); 084 } 085 086 /** 087 * Adds and returns a new bundle entry 088 */ 089 public BundleEntry addEntry() { 090 BundleEntry retVal = new BundleEntry(); 091 getEntries().add(retVal); 092 return retVal; 093 } 094 095 /** 096 * Adds a new entry 097 * 098 * @param theBundleEntry 099 * The entry to add 100 */ 101 public void addEntry(BundleEntry theBundleEntry) { 102 Validate.notNull(theBundleEntry, "theBundleEntry can not be null"); 103 getEntries().add(theBundleEntry); 104 } 105 106 /** 107 * Creates a new entry using the given resource and populates it accordingly 108 * 109 * @param theResource 110 * The resource to add 111 * @return Returns the newly created bundle entry that was added to the bundle 112 */ 113 public BundleEntry addResource(IResource theResource, FhirContext theContext, String theServerBase) { 114 BundleEntry entry = addEntry(); 115 entry.setResource(theResource); 116 117 RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); 118 119 String title = ResourceMetadataKeyEnum.TITLE.get(theResource); 120 if (title != null) { 121 entry.getTitle().setValue(title); 122 } else { 123 entry.getTitle().setValue(def.getName() + " " + StringUtils.defaultString(theResource.getId().getValue(), "(no ID)")); 124 } 125 126 if (theResource.getId() != null) { 127 if (theResource.getId().isAbsolute()) { 128 129 entry.getLinkSelf().setValue(theResource.getId().getValue()); 130 //TODO: Use of a deprecated method should be resolved. 131 entry.getId().setValue(theResource.getId().toVersionless().getValue()); 132 133 } else if (StringUtils.isNotBlank(theResource.getId().getValue())) { 134 135 StringBuilder b = new StringBuilder(); 136 b.append(theServerBase); 137 if (b.length() > 0 && b.charAt(b.length() - 1) != '/') { 138 b.append('/'); 139 } 140 b.append(def.getName()); 141 b.append('/'); 142 String resId = theResource.getId().getIdPart(); 143 b.append(resId); 144 145 //TODO: Use of a deprecated method should be resolved. 146 entry.getId().setValue(b.toString()); 147 148 if (isNotBlank(theResource.getId().getVersionIdPart())) { 149 b.append('/'); 150 b.append(Constants.PARAM_HISTORY); 151 b.append('/'); 152 b.append(theResource.getId().getVersionIdPart()); 153 } else { 154 //TODO: Use of a deprecated method should be resolved. 155 IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(theResource); 156 if (versionId != null) { 157 b.append('/'); 158 b.append(Constants.PARAM_HISTORY); 159 b.append('/'); 160 b.append(versionId.getValue()); 161 } 162 } 163 164 String qualifiedId = b.toString(); 165 entry.getLinkSelf().setValue(qualifiedId); 166 167 // String resourceType = theContext.getResourceDefinition(theResource).getName(); 168 169 170 } 171 } 172 173 InstantDt published = ResourceMetadataKeyEnum.PUBLISHED.get(theResource); 174 if (published == null) { 175 entry.getPublished().setToCurrentTimeInLocalTimeZone(); 176 } else { 177 entry.setPublished(published); 178 } 179 180 InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(theResource); 181 if (updated != null) { 182 //TODO: Use of a deprecated method should be resolved. 183 entry.setUpdated(updated); 184 } 185 186 InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(theResource); 187 if (deleted != null) { 188 entry.setDeleted(deleted); 189 } 190 191 IdDt previous = ResourceMetadataKeyEnum.PREVIOUS_ID.get(theResource); 192 if (previous != null) { 193 entry.getLinkAlternate().setValue(previous.withServerBase(theServerBase, def.getName()).getValue()); 194 } 195 196 TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); 197 if (tagList != null) { 198 for (Tag nextTag : tagList) { 199 entry.addCategory(nextTag); 200 } 201 } 202 203 String linkSearch = ResourceMetadataKeyEnum.LINK_SEARCH.get(theResource); 204 if (isNotBlank(linkSearch)) { 205 if (!UrlUtil.isAbsolute(linkSearch)) { 206 linkSearch = (theServerBase + "/" + linkSearch); 207 } 208 entry.getLinkSearch().setValue(linkSearch); 209 } 210 211 String linkAlternate = ResourceMetadataKeyEnum.LINK_ALTERNATE.get(theResource); 212 if (isNotBlank(linkAlternate)) { 213 if (!UrlUtil.isAbsolute(linkAlternate)) { 214 linkSearch = (theServerBase + "/" + linkAlternate); 215 } 216 entry.getLinkAlternate().setValue(linkSearch); 217 } 218 219 BundleEntrySearchModeEnum entryStatus = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(theResource); 220 if (entryStatus != null) { 221 entry.getSearchMode().setValueAsEnum(entryStatus); 222 } 223 224 BundleEntryTransactionMethodEnum entryTransactionOperation = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(theResource); 225 if (entryTransactionOperation != null) { 226 entry.getTransactionMethod().setValueAsEnum(entryTransactionOperation); 227 } 228 229 DecimalDt entryScore = ResourceMetadataKeyEnum.ENTRY_SCORE.get(theResource); 230 if (entryScore != null) { 231 entry.setScore(entryScore); 232 } 233 234 return entry; 235 } 236 237 public StringDt getBundleId() { 238 if (myBundleId == null) { 239 myBundleId = new StringDt(); 240 } 241 return myBundleId; 242 } 243 244 public TagList getCategories() { 245 if (myCategories == null) { 246 myCategories = new TagList(); 247 } 248 return myCategories; 249 } 250 251 public List<BundleEntry> getEntries() { 252 if (myEntries == null) { 253 myEntries = new ArrayList<BundleEntry>(); 254 } 255 return myEntries; 256 } 257 258 public StringDt getLinkBase() { 259 if (myLinkBase == null) { 260 myLinkBase = new StringDt(); 261 } 262 return myLinkBase; 263 } 264 265 public StringDt getLinkFirst() { 266 if (myLinkFirst == null) { 267 myLinkFirst = new StringDt(); 268 } 269 return myLinkFirst; 270 } 271 272 public StringDt getLinkLast() { 273 if (myLinkLast == null) { 274 myLinkLast = new StringDt(); 275 } 276 return myLinkLast; 277 } 278 279 public StringDt getLinkNext() { 280 if (myLinkNext == null) { 281 myLinkNext = new StringDt(); 282 } 283 return myLinkNext; 284 } 285 286 public StringDt getLinkPrevious() { 287 if (myLinkPrevious == null) { 288 myLinkPrevious = new StringDt(); 289 } 290 return myLinkPrevious; 291 } 292 293 public StringDt getLinkSelf() { 294 if (myLinkSelf == null) { 295 myLinkSelf = new StringDt(); 296 } 297 return myLinkSelf; 298 } 299 300 /* 301 public InstantDt getPublished() { 302 InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); 303 if (retVal == null) { 304 retVal= new InstantDt(); 305 getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, retVal); 306 } 307 return retVal; 308 } 309 */ 310 311 /** 312 * Retrieves a resource from a bundle given its logical ID. 313 * <p> 314 * <b>Important usage notes</b>: This method ignores base URLs (so passing in an ID of 315 * <code>http://foo/Patient/123</code> will return a resource if it has the logical ID of 316 * <code>http://bar/Patient/123</code>. Also, this method is intended to be used for bundles which have already been 317 * populated. It will cache its results for fast performance, but that means that modifications to the bundle after 318 * this method is called may not be accurately reflected. 319 * </p> 320 * 321 * @param theId 322 * The resource ID 323 * @return Returns the resource with the given ID, or <code>null</code> if none is found 324 */ 325 public IResource getResourceById(IdDt theId) { 326 Map<IdDt, IResource> map = myIdToEntries; 327 if (map == null) { 328 map = new HashMap<IdDt, IResource>(); 329 for (BundleEntry next : this.getEntries()) { 330 //TODO: Use of a deprecated method should be resolved. 331 if (next.getId().isEmpty() == false) { 332 map.put(next.getId().toUnqualified(), next.getResource()); 333 } 334 } 335 myIdToEntries = map; 336 } 337 return map.get(theId.toUnqualified()); 338 } 339 340 public ResourceMetadataMap getResourceMetadata() { 341 if (myResourceMetadata == null) { 342 myResourceMetadata = new ResourceMetadataMap(); 343 } 344 return myResourceMetadata; 345 } 346 347 /** 348 * Returns a list containing all resources of the given type from this bundle 349 */ 350 public <T extends IResource> List<T> getResources(Class<T> theClass) { 351 ArrayList<T> retVal = new ArrayList<T>(); 352 for (BundleEntry next : getEntries()) { 353 if (next.getResource() != null && theClass.isAssignableFrom(next.getResource().getClass())) { 354 @SuppressWarnings("unchecked") 355 T resource = (T) next.getResource(); 356 retVal.add(resource); 357 } 358 } 359 return retVal; 360 } 361 362 public StringDt getTitle() { 363 if (myTitle == null) { 364 myTitle = new StringDt(); 365 } 366 return myTitle; 367 } 368 369 public IntegerDt getTotalResults() { 370 if (myTotalResults == null) { 371 myTotalResults = new IntegerDt(); 372 } 373 return myTotalResults; 374 } 375 376 public BoundCodeDt<BundleTypeEnum> getType() { 377 if (myType == null) { 378 myType = new BoundCodeDt<BundleTypeEnum>(BundleTypeEnum.VALUESET_BINDER); 379 } 380 return myType; 381 } 382 383 public InstantDt getUpdated() { 384 InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); 385 if (retVal == null) { 386 retVal= new InstantDt(); 387 getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, retVal); 388 } 389 return retVal; 390 } 391 392 /** 393 * Returns true if this bundle contains zero entries 394 */ 395 @Override 396 public boolean isEmpty() { 397 return getEntries().isEmpty(); 398 } 399 400 public void setCategories(TagList theCategories) { 401 myCategories = theCategories; 402 } 403 404 /* 405 public void setPublished(InstantDt thePublished) { 406 getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, thePublished); 407 } 408 */ 409 410 public void setType(BoundCodeDt<BundleTypeEnum> theType) { 411 myType = theType; 412 } 413 414 /** 415 * Returns the number of entries in this bundle 416 */ 417 public int size() { 418 return getEntries().size(); 419 } 420 421 public List<IResource> toListOfResources() { 422 ArrayList<IResource> retVal = new ArrayList<IResource>(); 423 for (BundleEntry next : getEntries()) { 424 if (next.getResource() != null) { 425 retVal.add(next.getResource()); 426 } 427 } 428 return retVal; 429 } 430 431 @Override 432 public String toString() { 433 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 434 b.append(getEntries().size() + " entries"); 435 b.append("id", getId()); 436 return b.toString(); 437 } 438 439 public static Bundle withResources(List<IResource> theResources, FhirContext theContext, String theServerBase) { 440 Bundle retVal = new Bundle(); 441 for (IResource next : theResources) { 442 retVal.addResource(next, theContext, theServerBase); 443 } 444 return retVal; 445 } 446 447 public static Bundle withSingleResource(IResource theResource) { 448 Bundle retVal = new Bundle(); 449 retVal.addEntry().setResource(theResource); 450 return retVal; 451 } 452 453}