
001/* 002 * #%L 003 * HAPI FHIR JPA Model 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.model.entity; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; 024import ca.uhn.fhir.jpa.model.cross.IResourceLookup; 025import ca.uhn.fhir.jpa.model.dao.JpaPid; 026import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData; 027import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder; 028import ca.uhn.fhir.jpa.model.search.SearchParamTextPropertyBinder; 029import ca.uhn.fhir.model.primitive.IdDt; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 032import org.apache.commons.lang3.builder.ToStringBuilder; 033import org.apache.commons.lang3.builder.ToStringStyle; 034import org.hibernate.annotations.GenericGenerator; 035import org.hibernate.Session; 036import org.hibernate.annotations.GenerationTime; 037import org.hibernate.annotations.GeneratorType; 038import org.hibernate.annotations.OptimisticLock; 039import org.hibernate.search.engine.backend.types.Projectable; 040import org.hibernate.search.engine.backend.types.Searchable; 041import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef; 042import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingBinderRef; 043import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; 044import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; 045import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; 046import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency; 047import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath; 048import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding; 049import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue; 050import org.hibernate.tuple.ValueGenerator; 051import org.hl7.fhir.instance.model.api.IIdType; 052 053import javax.persistence.CascadeType; 054import javax.persistence.Column; 055import javax.persistence.Entity; 056import javax.persistence.FetchType; 057import javax.persistence.GeneratedValue; 058import javax.persistence.GenerationType; 059import javax.persistence.Id; 060import javax.persistence.Index; 061import javax.persistence.NamedEntityGraph; 062import javax.persistence.OneToMany; 063import javax.persistence.OneToOne; 064import javax.persistence.PrePersist; 065import javax.persistence.PreUpdate; 066import org.hibernate.annotations.GenericGenerator; 067import javax.persistence.Table; 068import javax.persistence.Transient; 069import javax.persistence.Version; 070import java.io.Serializable; 071import java.util.ArrayList; 072import java.util.Collection; 073import java.util.HashSet; 074import java.util.Objects; 075import java.util.Set; 076import java.util.stream.Collectors; 077 078@Indexed(routingBinder= @RoutingBinderRef(type = ResourceTableRoutingBinder.class)) 079@Entity 080@Table(name = ResourceTable.HFJ_RESOURCE, uniqueConstraints = {}, indexes = { 081 // Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE 082 @Index(name = "IDX_RES_DATE", columnList = BaseHasResource.RES_UPDATED), 083 @Index(name = "IDX_RES_TYPE_DEL_UPDATED", columnList = "RES_TYPE,RES_DELETED_AT,RES_UPDATED,PARTITION_ID,RES_ID"), 084}) 085@NamedEntityGraph(name = "Resource.noJoins") 086public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource<JpaPid> { 087 public static final int RESTYPE_LEN = 40; 088 private static final int MAX_LANGUAGE_LENGTH = 20; 089 private static final long serialVersionUID = 1L; 090 public static final String HFJ_RESOURCE = "HFJ_RESOURCE"; 091 public static final String RES_TYPE = "RES_TYPE"; 092 093 /** 094 * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB 095 * Note the extra config needed in HS6 for indexing transient props: 096 * https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#indexed-transient-requires-configuration 097 * 098 * Note that we depend on `myVersion` updated for this field to be indexed. 099 */ 100 @Transient 101 @FullTextField(name = "myContentText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer") 102 @FullTextField(name = "myContentTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteEdgeAnalyzer") 103 @FullTextField(name = "myContentTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteNGramAnalyzer") 104 @FullTextField(name = "myContentTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompletePhoneticAnalyzer") 105 @OptimisticLock(excluded = true) 106 @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion"))) 107 private String myContentText; 108 109 @Column(name = "HASH_SHA256", length = 64, nullable = true) 110 @OptimisticLock(excluded = true) 111 private String myHashSha256; 112 113 @Column(name = "SP_HAS_LINKS") 114 @OptimisticLock(excluded = true) 115 private boolean myHasLinks; 116 117 @Id 118 @GenericGenerator(name = "SEQ_RESOURCE_ID", strategy = "ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator") 119 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_ID") 120 @Column(name = "RES_ID") 121 @GenericField(projectable = Projectable.YES) 122 private Long myId; 123 124 @Column(name = "SP_INDEX_STATUS", nullable = true) 125 @OptimisticLock(excluded = true) 126 private Long myIndexStatus; 127 128 // TODO: Removed in 5.5.0. Drop in a future release. 129 @Column(name = "RES_LANGUAGE", length = MAX_LANGUAGE_LENGTH, nullable = true) 130 @OptimisticLock(excluded = true) 131 private String myLanguage; 132 133 /** 134 * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB 135 */ 136 @Transient() 137 @FullTextField(name = "myNarrativeText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer") 138 @FullTextField(name = "myNarrativeTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteEdgeAnalyzer") 139 @FullTextField(name = "myNarrativeTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteNGramAnalyzer") 140 @FullTextField(name = "myNarrativeTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompletePhoneticAnalyzer") 141 @OptimisticLock(excluded = true) 142 @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion"))) 143 private String myNarrativeText; 144 145 @Transient 146 @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion"))) 147 @PropertyBinding(binder = @PropertyBinderRef(type = SearchParamTextPropertyBinder.class)) 148 private ExtendedHSearchIndexData myLuceneIndexData; 149 150 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 151 @OptimisticLock(excluded = true) 152 private Collection<ResourceIndexedSearchParamCoords> myParamsCoords; 153 154 @Column(name = "SP_COORDS_PRESENT") 155 @OptimisticLock(excluded = true) 156 private boolean myParamsCoordsPopulated; 157 158 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 159 @OptimisticLock(excluded = true) 160 private Collection<ResourceIndexedSearchParamDate> myParamsDate; 161 162 @Column(name = "SP_DATE_PRESENT") 163 @OptimisticLock(excluded = true) 164 private boolean myParamsDatePopulated; 165 166 @OptimisticLock(excluded = true) 167 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 168 private Collection<ResourceIndexedSearchParamNumber> myParamsNumber; 169 170 @Column(name = "SP_NUMBER_PRESENT") 171 @OptimisticLock(excluded = true) 172 private boolean myParamsNumberPopulated; 173 174 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 175 @OptimisticLock(excluded = true) 176 private Collection<ResourceIndexedSearchParamQuantity> myParamsQuantity; 177 178 @Column(name = "SP_QUANTITY_PRESENT") 179 @OptimisticLock(excluded = true) 180 private boolean myParamsQuantityPopulated; 181 182 /** 183 * Added to support UCUM conversion 184 * since 5.3.0 185 */ 186 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 187 @OptimisticLock(excluded = true) 188 private Collection<ResourceIndexedSearchParamQuantityNormalized> myParamsQuantityNormalized; 189 190 /** 191 * Added to support UCUM conversion, 192 * NOTE : use Boolean class instead of boolean primitive, in order to set the existing rows to null 193 * since 5.3.0 194 */ 195 @Column(name = "SP_QUANTITY_NRML_PRESENT") 196 @OptimisticLock(excluded = true) 197 private Boolean myParamsQuantityNormalizedPopulated = Boolean.FALSE; 198 199 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 200 @OptimisticLock(excluded = true) 201 private Collection<ResourceIndexedSearchParamString> myParamsString; 202 203 @Column(name = "SP_STRING_PRESENT") 204 @OptimisticLock(excluded = true) 205 private boolean myParamsStringPopulated; 206 207 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 208 @OptimisticLock(excluded = true) 209 private Collection<ResourceIndexedSearchParamToken> myParamsToken; 210 211 @Column(name = "SP_TOKEN_PRESENT") 212 @OptimisticLock(excluded = true) 213 private boolean myParamsTokenPopulated; 214 215 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 216 @OptimisticLock(excluded = true) 217 private Collection<ResourceIndexedSearchParamUri> myParamsUri; 218 219 @Column(name = "SP_URI_PRESENT") 220 @OptimisticLock(excluded = true) 221 private boolean myParamsUriPopulated; 222 223 // Added in 3.0.0 - Should make this a primitive Boolean at some point 224 @OptimisticLock(excluded = true) 225 @Column(name = "SP_CMPSTR_UNIQ_PRESENT") 226 private Boolean myParamsComboStringUniquePresent = false; 227 228 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 229 @OptimisticLock(excluded = true) 230 private Collection<ResourceIndexedComboStringUnique> myParamsComboStringUnique; 231 232 // Added in 5.5.0 - Should make this a primitive Boolean at some point 233 @OptimisticLock(excluded = true) 234 @Column(name = "SP_CMPTOKS_PRESENT") 235 private Boolean myParamsComboTokensNonUniquePresent = false; 236 237 @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 238 @OptimisticLock(excluded = true) 239 private Collection<ResourceIndexedComboTokenNonUnique> myParamsComboTokensNonUnique; 240 241 @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 242 @OptimisticLock(excluded = true) 243 private Collection<ResourceLink> myResourceLinks; 244 245 /** 246 * This is a clone of {@link #myResourceLinks} but without the hibernate annotations. 247 * Before we persist we copy the contents of {@link #myResourceLinks} into this field. We 248 * have this separate because that way we can only populate this field if 249 * {@link #myHasLinks} is true, meaning that there are actually resource links present 250 * right now. This avoids Hibernate Search triggering a select on the resource link 251 * table. 252 * <p> 253 * This field is used by FulltextSearchSvcImpl 254 * <p> 255 * You can test that any changes don't cause extra queries by running 256 * FhirResourceDaoR4QueryCountTest 257 */ 258 @FullTextField 259 @Transient 260 @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myResourceLinks"))) 261 private String myResourceLinksField; 262 263 @OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) 264 @OptimisticLock(excluded = true) 265 private Collection<ResourceLink> myResourceLinksAsTarget; 266 267 @Column(name = RES_TYPE, length = RESTYPE_LEN, nullable = false) 268 @FullTextField 269 @OptimisticLock(excluded = true) 270 private String myResourceType; 271 272 @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) 273 @OptimisticLock(excluded = true) 274 private Collection<SearchParamPresentEntity> mySearchParamPresents; 275 276 @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) 277 @OptimisticLock(excluded = true) 278 private Set<ResourceTag> myTags; 279 280 @Transient 281 private transient boolean myUnchangedInCurrentOperation; 282 283 284 /** 285 * The id of the Resource. 286 * Will contain either the client-assigned id, or the sequence value. 287 * Will be null during insert time until the first read. 288 * 289 */ 290 @Column(name= "FHIR_ID", 291 // [A-Za-z0-9\-\.]{1,64} - https://www.hl7.org/fhir/datatypes.html#id 292 length = 64, 293 // we never update this after insert, and the Generator will otherwise "dirty" the object. 294 updatable = false) 295 // inject the pk for server-assigned sequence ids. 296 @GeneratorType(when = GenerationTime.INSERT, type = FhirIdGenerator.class) 297 // Make sure the generator doesn't bump the history version. 298 @OptimisticLock(excluded = true) 299 private String myFhirId; 300 301 /** 302 * Is there a corresponding row in {@link ResourceSearchUrlEntity} for 303 * this row. 304 * TODO: Added in 6.6.0 - Should make this a primitive boolean at some point 305 */ 306 @OptimisticLock(excluded = true) 307 @Column(name = "SEARCH_URL_PRESENT", nullable = true) 308 private Boolean mySearchUrlPresent = false; 309 310 /** 311 * Populate myFhirId with server-assigned sequence id when no client-id provided. 312 * We eat this complexity during insert to simplify query time with a uniform column. 313 * Server-assigned sequence ids aren't available until just before insertion. 314 * Hibernate calls insert Generators after the pk has been assigned, so we can use myId safely here. 315 */ 316 public static final class FhirIdGenerator implements ValueGenerator<String> { 317 @Override 318 public String generateValue(Session session, Object owner) { 319 ResourceTable that = (ResourceTable) owner; 320 return that.myFhirId != null ? that.myFhirId : that.myId.toString(); 321 } 322 } 323 324 @Version 325 @Column(name = "RES_VER") 326 private long myVersion; 327 328 @OneToMany(mappedBy = "myResourceTable", fetch = FetchType.LAZY) 329 private Collection<ResourceHistoryProvenanceEntity> myProvenance; 330 331 @Transient 332 private transient ResourceHistoryTable myCurrentVersionEntity; 333 334 @OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource") 335 @OptimisticLock(excluded = true) 336 private ForcedId myForcedId; 337 338 @Transient 339 private volatile String myCreatedByMatchUrl; 340 341 /** 342 * Constructor 343 */ 344 public ResourceTable() { 345 super(); 346 } 347 348 @Override 349 public ResourceTag addTag(TagDefinition theTag) { 350 for (ResourceTag next : getTags()) { 351 if (next.getTag().equals(theTag)) { 352 return next; 353 } 354 } 355 ResourceTag tag = new ResourceTag(this, theTag, getPartitionId()); 356 getTags().add(tag); 357 return tag; 358 } 359 360 361 public String getHashSha256() { 362 return myHashSha256; 363 } 364 365 public void setHashSha256(String theHashSha256) { 366 myHashSha256 = theHashSha256; 367 } 368 369 @Override 370 public Long getId() { 371 return myId; 372 } 373 374 public void setId(Long theId) { 375 myId = theId; 376 } 377 378 public Long getIndexStatus() { 379 return myIndexStatus; 380 } 381 382 public void setIndexStatus(Long theIndexStatus) { 383 myIndexStatus = theIndexStatus; 384 } 385 386 public Collection<ResourceIndexedComboStringUnique> getParamsComboStringUnique() { 387 if (myParamsComboStringUnique == null) { 388 myParamsComboStringUnique = new ArrayList<>(); 389 } 390 return myParamsComboStringUnique; 391 } 392 393 public Collection<ResourceIndexedComboTokenNonUnique> getmyParamsComboTokensNonUnique() { 394 if (myParamsComboTokensNonUnique == null) { 395 myParamsComboTokensNonUnique = new ArrayList<>(); 396 } 397 return myParamsComboTokensNonUnique; 398 } 399 400 public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() { 401 if (myParamsCoords == null) { 402 myParamsCoords = new ArrayList<>(); 403 } 404 return myParamsCoords; 405 } 406 407 public void setParamsCoords(Collection<ResourceIndexedSearchParamCoords> theParamsCoords) { 408 if (!isParamsTokenPopulated() && theParamsCoords.isEmpty()) { 409 return; 410 } 411 getParamsCoords().clear(); 412 getParamsCoords().addAll(theParamsCoords); 413 } 414 415 public Collection<ResourceIndexedSearchParamDate> getParamsDate() { 416 if (myParamsDate == null) { 417 myParamsDate = new ArrayList<>(); 418 } 419 return myParamsDate; 420 } 421 422 public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) { 423 if (!isParamsDatePopulated() && theParamsDate.isEmpty()) { 424 return; 425 } 426 getParamsDate().clear(); 427 getParamsDate().addAll(theParamsDate); 428 } 429 430 public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() { 431 if (myParamsNumber == null) { 432 myParamsNumber = new ArrayList<>(); 433 } 434 return myParamsNumber; 435 } 436 437 public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) { 438 if (!isParamsNumberPopulated() && theNumberParams.isEmpty()) { 439 return; 440 } 441 getParamsNumber().clear(); 442 getParamsNumber().addAll(theNumberParams); 443 } 444 445 public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() { 446 if (myParamsQuantity == null) { 447 myParamsQuantity = new ArrayList<>(); 448 } 449 return myParamsQuantity; 450 } 451 452 public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) { 453 if (!isParamsQuantityPopulated() && theQuantityParams.isEmpty()) { 454 return; 455 } 456 getParamsQuantity().clear(); 457 getParamsQuantity().addAll(theQuantityParams); 458 } 459 460 public Collection<ResourceIndexedSearchParamQuantityNormalized> getParamsQuantityNormalized() { 461 if (myParamsQuantityNormalized == null) { 462 myParamsQuantityNormalized = new ArrayList<>(); 463 } 464 return myParamsQuantityNormalized; 465 } 466 467 public void setParamsQuantityNormalized(Collection<ResourceIndexedSearchParamQuantityNormalized> theQuantityNormalizedParams) { 468 if (!isParamsQuantityNormalizedPopulated() && theQuantityNormalizedParams.isEmpty()) { 469 return; 470 } 471 getParamsQuantityNormalized().clear(); 472 getParamsQuantityNormalized().addAll(theQuantityNormalizedParams); 473 } 474 475 public Collection<ResourceIndexedSearchParamString> getParamsString() { 476 if (myParamsString == null) { 477 myParamsString = new ArrayList<>(); 478 } 479 return myParamsString; 480 } 481 482 public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) { 483 if (!isParamsStringPopulated() && theParamsString.isEmpty()) { 484 return; 485 } 486 getParamsString().clear(); 487 getParamsString().addAll(theParamsString); 488 } 489 490 public Collection<ResourceIndexedSearchParamToken> getParamsToken() { 491 if (myParamsToken == null) { 492 myParamsToken = new ArrayList<>(); 493 } 494 return myParamsToken; 495 } 496 497 public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) { 498 if (!isParamsTokenPopulated() && theParamsToken.isEmpty()) { 499 return; 500 } 501 getParamsToken().clear(); 502 getParamsToken().addAll(theParamsToken); 503 } 504 505 public Collection<ResourceIndexedSearchParamUri> getParamsUri() { 506 if (myParamsUri == null) { 507 myParamsUri = new ArrayList<>(); 508 } 509 return myParamsUri; 510 } 511 512 public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) { 513 if (!isParamsTokenPopulated() && theParamsUri.isEmpty()) { 514 return; 515 } 516 getParamsUri().clear(); 517 getParamsUri().addAll(theParamsUri); 518 } 519 520 @Override 521 public Long getResourceId() { 522 return getId(); 523 } 524 525 public Collection<ResourceLink> getResourceLinks() { 526 if (myResourceLinks == null) { 527 myResourceLinks = new ArrayList<>(); 528 } 529 return myResourceLinks; 530 } 531 532 public void setResourceLinks(Collection<ResourceLink> theLinks) { 533 if (!isHasLinks() && theLinks.isEmpty()) { 534 return; 535 } 536 getResourceLinks().clear(); 537 getResourceLinks().addAll(theLinks); 538 } 539 540 @Override 541 public String getResourceType() { 542 return myResourceType; 543 } 544 545 public ResourceTable setResourceType(String theResourceType) { 546 myResourceType = theResourceType; 547 return this; 548 } 549 550 @Override 551 public Collection<ResourceTag> getTags() { 552 if (myTags == null) { 553 myTags = new HashSet<>(); 554 } 555 return myTags; 556 } 557 558 @Override 559 public long getVersion() { 560 return myVersion; 561 } 562 563 @Override 564 public boolean isDeleted() { 565 return getDeleted() != null; 566 } 567 568 @Override 569 public void setNotDeleted() { 570 setDeleted(null); 571 } 572 573 public void setVersion(long theVersion) { 574 myVersion = theVersion; 575 } 576 577 public boolean isHasLinks() { 578 return myHasLinks; 579 } 580 581 public void setHasLinks(boolean theHasLinks) { 582 myHasLinks = theHasLinks; 583 } 584 585 public boolean isParamsComboStringUniquePresent() { 586 if (myParamsComboStringUniquePresent == null) { 587 return false; 588 } 589 return myParamsComboStringUniquePresent; 590 } 591 592 public void setParamsComboStringUniquePresent(boolean theParamsComboStringUniquePresent) { 593 myParamsComboStringUniquePresent = theParamsComboStringUniquePresent; 594 } 595 596 public boolean isParamsComboTokensNonUniquePresent() { 597 if (myParamsComboTokensNonUniquePresent == null) { 598 return false; 599 } 600 return myParamsComboTokensNonUniquePresent; 601 } 602 603 public void setParamsComboTokensNonUniquePresent(boolean theParamsComboTokensNonUniquePresent) { 604 myParamsComboStringUniquePresent = theParamsComboTokensNonUniquePresent; 605 } 606 607 public boolean isParamsCoordsPopulated() { 608 return myParamsCoordsPopulated; 609 } 610 611 public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) { 612 myParamsCoordsPopulated = theParamsCoordsPopulated; 613 } 614 615 public boolean isParamsDatePopulated() { 616 return myParamsDatePopulated; 617 } 618 619 public void setParamsDatePopulated(boolean theParamsDatePopulated) { 620 myParamsDatePopulated = theParamsDatePopulated; 621 } 622 623 public boolean isParamsNumberPopulated() { 624 return myParamsNumberPopulated; 625 } 626 627 public void setParamsNumberPopulated(boolean theParamsNumberPopulated) { 628 myParamsNumberPopulated = theParamsNumberPopulated; 629 } 630 631 public boolean isParamsQuantityPopulated() { 632 return myParamsQuantityPopulated; 633 } 634 635 public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) { 636 myParamsQuantityPopulated = theParamsQuantityPopulated; 637 } 638 639 public Boolean isParamsQuantityNormalizedPopulated() { 640 if (myParamsQuantityNormalizedPopulated == null) 641 return Boolean.FALSE; 642 else 643 return myParamsQuantityNormalizedPopulated; 644 } 645 646 public void setParamsQuantityNormalizedPopulated(Boolean theParamsQuantityNormalizedPopulated) { 647 if (theParamsQuantityNormalizedPopulated == null) 648 myParamsQuantityNormalizedPopulated = Boolean.FALSE; 649 else 650 myParamsQuantityNormalizedPopulated = theParamsQuantityNormalizedPopulated; 651 } 652 653 public boolean isParamsStringPopulated() { 654 return myParamsStringPopulated; 655 } 656 657 public void setParamsStringPopulated(boolean theParamsStringPopulated) { 658 myParamsStringPopulated = theParamsStringPopulated; 659 } 660 661 public boolean isParamsTokenPopulated() { 662 return myParamsTokenPopulated; 663 } 664 665 public void setParamsTokenPopulated(boolean theParamsTokenPopulated) { 666 myParamsTokenPopulated = theParamsTokenPopulated; 667 } 668 669 public boolean isParamsUriPopulated() { 670 return myParamsUriPopulated; 671 } 672 673 public void setParamsUriPopulated(boolean theParamsUriPopulated) { 674 myParamsUriPopulated = theParamsUriPopulated; 675 } 676 677 /** 678 * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation 679 * and was not re-saved in the database 680 */ 681 public boolean isUnchangedInCurrentOperation() { 682 return myUnchangedInCurrentOperation; 683 } 684 685 /** 686 * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation 687 * and was not re-saved in the database 688 */ 689 public void setUnchangedInCurrentOperation(boolean theUnchangedInCurrentOperation) { 690 691 myUnchangedInCurrentOperation = theUnchangedInCurrentOperation; 692 } 693 694 public void setContentText(String theContentText) { 695 myContentText = theContentText; 696 } 697 698 public String getContentText() { 699 return myContentText; 700 } 701 702 public void setNarrativeText(String theNarrativeText) { 703 myNarrativeText = theNarrativeText; 704 } 705 706 public boolean isSearchUrlPresent() { 707 return Boolean.TRUE.equals(mySearchUrlPresent); 708 } 709 710 public void setSearchUrlPresent(boolean theSearchUrlPresent) { 711 mySearchUrlPresent = theSearchUrlPresent; 712 } 713 714 public ResourceHistoryTable toHistory(boolean theCreateVersionTags) { 715 ResourceHistoryTable retVal = new ResourceHistoryTable(); 716 717 retVal.setResourceId(myId); 718 retVal.setResourceType(myResourceType); 719 retVal.setVersion(myVersion); 720 retVal.setTransientForcedId(getTransientForcedId()); 721 722 retVal.setPublished(getPublishedDate()); 723 retVal.setUpdated(getUpdatedDate()); 724 retVal.setFhirVersion(getFhirVersion()); 725 retVal.setDeleted(getDeleted()); 726 retVal.setResourceTable(this); 727 retVal.setForcedId(getForcedId()); 728 retVal.setPartitionId(getPartitionId()); 729 730 retVal.getTags().clear(); 731 732 retVal.setHasTags(isHasTags()); 733 if (isHasTags() && theCreateVersionTags) { 734 for (ResourceTag next : getTags()) { 735 retVal.addTag(next); 736 } 737 } 738 739 return retVal; 740 } 741 742 @Override 743 public String toString() { 744 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 745 b.append("pid", myId); 746 b.append("resourceType", myResourceType); 747 b.append("version", myVersion); 748 if (getPartitionId() != null) { 749 b.append("partitionId", getPartitionId().getPartitionId()); 750 } 751 b.append("lastUpdated", getUpdated().getValueAsString()); 752 if (getDeleted() != null) { 753 b.append("deleted"); 754 } 755 return b.build(); 756 } 757 758 @PrePersist 759 @PreUpdate 760 public void preSave() { 761 if (myHasLinks && myResourceLinks != null) { 762 myResourceLinksField = getResourceLinks() 763 .stream() 764 .map(ResourceLink::getTargetResourcePid) 765 .filter(Objects::nonNull) 766 .map(Object::toString) 767 .collect(Collectors.joining(" ")); 768 } else { 769 myResourceLinksField = null; 770 } 771 } 772 773 /** 774 * This is a convenience to avoid loading the version a second time within a single transaction. It is 775 * not persisted. 776 */ 777 public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) { 778 myCurrentVersionEntity = theCurrentVersionEntity; 779 } 780 781 /** 782 * This is a convenience to avoid loading the version a second time within a single transaction. It is 783 * not persisted. 784 */ 785 public ResourceHistoryTable getCurrentVersionEntity() { 786 return myCurrentVersionEntity; 787 } 788 789 @Override 790 public JpaPid getPersistentId() { 791 return JpaPid.fromId(getId()); 792 } 793 794 @Override 795 public ForcedId getForcedId() { 796 return myForcedId; 797 } 798 799 @Override 800 public void setForcedId(ForcedId theForcedId) { 801 myForcedId = theForcedId; 802 } 803 804 805 806 @Override 807 public IdDt getIdDt() { 808 IdDt retVal = new IdDt(); 809 populateId(retVal); 810 return retVal; 811 } 812 813 814 public IIdType getIdType(FhirContext theContext) { 815 IIdType retVal = theContext.getVersion().newIdType(); 816 populateId(retVal); 817 return retVal; 818 } 819 820 private void populateId(IIdType retVal) { 821 if (getTransientForcedId() != null) { 822 // Avoid a join query if possible 823 retVal.setValue(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); 824 } else if (getForcedId() == null) { 825 Long id = this.getResourceId(); 826 retVal.setValue(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); 827 } else { 828 String forcedId = getForcedId().getForcedId(); 829 retVal.setValue(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); 830 } 831 } 832 833 public void setCreatedByMatchUrl(String theCreatedByMatchUrl) { 834 myCreatedByMatchUrl = theCreatedByMatchUrl; 835 } 836 837 public String getCreatedByMatchUrl() { 838 return myCreatedByMatchUrl; 839 } 840 841 public void setLuceneIndexData(ExtendedHSearchIndexData theLuceneIndexData) { 842 myLuceneIndexData = theLuceneIndexData; 843 } 844 845 public Collection<SearchParamPresentEntity> getSearchParamPresents() { 846 if (mySearchParamPresents == null) { 847 mySearchParamPresents = new ArrayList<>(); 848 } 849 return mySearchParamPresents; 850 } 851 852 /** 853 * Get the FHIR resource id. 854 * 855 * @return the resource id, or null if the resource doesn't have a client-assigned id, 856 * and hasn't been saved to the db to get a server-assigned id yet. 857 */ 858 public String getFhirId() { 859 return myFhirId; 860 } 861 862 public void setFhirId(String theFhirId) { 863 myFhirId = theFhirId; 864 } 865}