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