001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 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.entity; 021 022import ca.uhn.fhir.jpa.model.dao.JpaPid; 023import ca.uhn.fhir.jpa.model.entity.AuditableBasePartitionable; 024import ca.uhn.fhir.jpa.model.entity.ResourceTable; 025import ca.uhn.fhir.mdm.api.IMdmLink; 026import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; 027import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; 028import com.fasterxml.jackson.annotation.JsonIgnore; 029import jakarta.persistence.Column; 030import jakarta.persistence.Entity; 031import jakarta.persistence.EnumType; 032import jakarta.persistence.Enumerated; 033import jakarta.persistence.FetchType; 034import jakarta.persistence.ForeignKey; 035import jakarta.persistence.GeneratedValue; 036import jakarta.persistence.GenerationType; 037import jakarta.persistence.Id; 038import jakarta.persistence.Index; 039import jakarta.persistence.JoinColumn; 040import jakarta.persistence.JoinColumns; 041import jakarta.persistence.ManyToOne; 042import jakarta.persistence.SequenceGenerator; 043import jakarta.persistence.Table; 044import jakarta.persistence.Temporal; 045import jakarta.persistence.TemporalType; 046import jakarta.persistence.UniqueConstraint; 047import org.apache.commons.lang3.builder.ToStringBuilder; 048import org.hibernate.annotations.JdbcTypeCode; 049import org.hibernate.envers.AuditTable; 050import org.hibernate.envers.Audited; 051import org.hibernate.envers.NotAudited; 052import org.hibernate.type.SqlTypes; 053 054import java.util.Date; 055 056@Entity 057@Table( 058 name = "MPI_LINK", 059 uniqueConstraints = { 060 // TODO GGG DROP this index, and instead use the below one 061 @UniqueConstraint( 062 name = "IDX_EMPI_PERSON_TGT", 063 columnNames = {"PERSON_PID", "TARGET_PID"}), 064 // TODO GGG Should i make individual indices for PERSON/TARGET? 065 }, 066 indexes = { 067 @Index(name = "IDX_EMPI_MATCH_TGT_VER", columnList = "MATCH_RESULT, TARGET_PID, VERSION"), 068 // v---- this one 069 @Index(name = "IDX_EMPI_GR_TGT", columnList = "GOLDEN_RESOURCE_PID, TARGET_PID"), 070 @Index(name = "FK_EMPI_LINK_TARGET", columnList = "TARGET_PID"), 071 // indexes for metrics 072 @Index(name = "IDX_EMPI_TGT_MR_LS", columnList = "TARGET_TYPE, MATCH_RESULT, LINK_SOURCE"), 073 @Index(name = "IDX_EMPI_TGT_MR_SCORE", columnList = "TARGET_TYPE, MATCH_RESULT, SCORE") 074 }) 075@Audited 076// This is the table name generated by default by envers, but we set it explicitly for clarity 077@AuditTable("MPI_LINK_AUD") 078public class MdmLink extends AuditableBasePartitionable implements IMdmLink<JpaPid> { 079 public static final int VERSION_LENGTH = 16; 080 private static final int MATCH_RESULT_LENGTH = 16; 081 private static final int LINK_SOURCE_LENGTH = 16; 082 public static final int SOURCE_TYPE_LENGTH = 40; 083 public static final String GOLDEN_RESOURCE_PID = "GOLDEN_RESOURCE_PID"; 084 public static final String GOLDEN_RESOURCE_PARTITION_ID = "GOLDEN_RESOURCE_PARTITION_ID"; 085 public static final String PERSON_PID = "PERSON_PID"; 086 public static final String PERSON_PARTITION_ID = "PERSON_PARTITION_ID"; 087 public static final String TARGET_PID = "TARGET_PID"; 088 public static final String TARGET_PARTITION_ID = "TARGET_PARTITION_ID"; 089 090 @SequenceGenerator(name = "SEQ_EMPI_LINK_ID", sequenceName = "SEQ_EMPI_LINK_ID") 091 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_EMPI_LINK_ID") 092 @Id 093 @Column(name = "PID") 094 private Long myId; 095 096 @ManyToOne( 097 optional = false, 098 fetch = FetchType.LAZY, 099 cascade = {}) 100 @JoinColumns( 101 value = { 102 @JoinColumn( 103 name = GOLDEN_RESOURCE_PID, 104 referencedColumnName = "RES_ID", 105 insertable = false, 106 updatable = false, 107 nullable = false), 108 @JoinColumn( 109 name = GOLDEN_RESOURCE_PARTITION_ID, 110 referencedColumnName = "PARTITION_ID", 111 insertable = false, 112 updatable = false, 113 nullable = false) 114 }, 115 foreignKey = @ForeignKey(name = "FK_EMPI_LINK_GOLDEN_RESOURCE")) 116 @NotAudited 117 private ResourceTable myGoldenResource; 118 119 @Column(name = GOLDEN_RESOURCE_PID, nullable = false) 120 private Long myGoldenResourcePid; 121 122 @Column(name = GOLDEN_RESOURCE_PARTITION_ID, nullable = true) 123 private Integer myGoldenResourcePartitionId; 124 125 @Deprecated 126 @ManyToOne( 127 optional = false, 128 fetch = FetchType.LAZY, 129 cascade = {}) 130 @JoinColumns( 131 value = { 132 @JoinColumn( 133 name = PERSON_PID, 134 referencedColumnName = "RES_ID", 135 insertable = false, 136 updatable = false, 137 nullable = false), 138 @JoinColumn( 139 name = PERSON_PARTITION_ID, 140 referencedColumnName = "PARTITION_ID", 141 insertable = false, 142 updatable = false, 143 nullable = false) 144 }, 145 foreignKey = @ForeignKey(name = "FK_EMPI_LINK_PERSON")) 146 @NotAudited 147 private ResourceTable myPerson; 148 149 @Deprecated 150 @Column(name = "PERSON_PID", nullable = false) 151 private Long myPersonPid; 152 153 @Deprecated 154 @Column(name = PERSON_PARTITION_ID, nullable = true) 155 private Integer myPersonPartitionId; 156 157 @ManyToOne( 158 optional = false, 159 fetch = FetchType.LAZY, 160 cascade = {}) 161 @JoinColumns( 162 value = { 163 @JoinColumn( 164 name = TARGET_PID, 165 referencedColumnName = "RES_ID", 166 insertable = false, 167 updatable = false, 168 nullable = false), 169 @JoinColumn( 170 name = TARGET_PARTITION_ID, 171 referencedColumnName = "PARTITION_ID", 172 insertable = false, 173 updatable = false, 174 nullable = false) 175 }, 176 foreignKey = @ForeignKey(name = "FK_EMPI_LINK_TARGET")) 177 @NotAudited 178 private ResourceTable mySource; 179 180 @Column(name = "TARGET_PID", updatable = false, nullable = false) 181 private Long mySourcePid; 182 183 @Column(name = TARGET_PARTITION_ID, updatable = false, nullable = true) 184 private Integer mySourcePartitionId; 185 186 @Column(name = "MATCH_RESULT", nullable = false) 187 @Enumerated(EnumType.ORDINAL) 188 @JdbcTypeCode(SqlTypes.INTEGER) 189 private MdmMatchResultEnum myMatchResult; 190 191 @Column(name = "LINK_SOURCE", nullable = false) 192 @Enumerated(EnumType.ORDINAL) 193 @JdbcTypeCode(SqlTypes.INTEGER) 194 private MdmLinkSourceEnum myLinkSource; 195 196 @Temporal(TemporalType.TIMESTAMP) 197 @Column(name = "CREATED", nullable = false) 198 private Date myCreated; 199 200 @Temporal(TemporalType.TIMESTAMP) 201 @Column(name = "UPDATED", nullable = false) 202 private Date myUpdated; 203 204 @Column(name = "VERSION", nullable = false, length = VERSION_LENGTH) 205 private String myVersion; 206 207 /** This link was created as a result of an eid match **/ 208 @Column(name = "EID_MATCH") 209 private Boolean myEidMatch; 210 211 /** This link created a new person **/ 212 @Column(name = "NEW_PERSON") 213 private Boolean myHadToCreateNewGoldenResource; 214 215 @Column(name = "VECTOR") 216 @JsonIgnore 217 private Long myVector; 218 219 @Column(name = "SCORE") 220 private Double myScore; 221 222 // TODO GGG GL-1340 223 @Column(name = "RULE_COUNT") 224 private Long myRuleCount; 225 226 public MdmLink() {} 227 228 public MdmLink(String theVersion) { 229 myVersion = theVersion; 230 } 231 232 @Column(name = "TARGET_TYPE", nullable = true, length = SOURCE_TYPE_LENGTH) 233 private String myMdmSourceType; 234 235 @Override 236 public JpaPid getId() { 237 return JpaPid.fromId(myId); 238 } 239 240 @Override 241 public MdmLink setId(JpaPid theId) { 242 myId = theId.getId(); 243 return this; 244 } 245 246 @Override 247 public JpaPid getGoldenResourcePersistenceId() { 248 return JpaPid.fromId(myGoldenResourcePid); 249 } 250 251 @Override 252 public IMdmLink setGoldenResourcePersistenceId(JpaPid theGoldenResourcePid) { 253 Long longPid = theGoldenResourcePid.getId(); 254 setPersonPid(longPid); 255 256 myGoldenResourcePid = longPid; 257 myGoldenResourcePartitionId = theGoldenResourcePid.getPartitionId(); 258 return this; 259 } 260 261 @Override 262 public JpaPid getSourcePersistenceId() { 263 return JpaPid.fromId(mySourcePid); 264 } 265 266 @Override 267 public IMdmLink setSourcePersistenceId(JpaPid theSourcePid) { 268 mySourcePid = theSourcePid.getId(); 269 mySourcePartitionId = theSourcePid.getPartitionId(); 270 return this; 271 } 272 273 public ResourceTable getGoldenResource() { 274 return myGoldenResource; 275 } 276 277 public MdmLink setGoldenResource(ResourceTable theGoldenResource) { 278 myGoldenResource = theGoldenResource; 279 myGoldenResourcePid = theGoldenResource.getId().getId(); 280 myGoldenResourcePartitionId = theGoldenResource.getPersistentId().getPartitionId(); 281 282 myPerson = theGoldenResource; 283 myPersonPid = theGoldenResource.getId().getId(); 284 myPersonPartitionId = theGoldenResource.getPersistentId().getPartitionId(); 285 286 return this; 287 } 288 289 @Deprecated 290 public Long getGoldenResourcePid() { 291 return myGoldenResourcePid; 292 } 293 294 /** 295 * @deprecated Use {@link #setGoldenResourcePid(Long)} instead 296 */ 297 @Deprecated 298 public MdmLink setPersonPid(Long thePersonPid) { 299 myPersonPid = thePersonPid; 300 return this; 301 } 302 303 /** 304 * @deprecated Use {@link #setGoldenResourcePersistenceId(JpaPid)} instead 305 */ 306 @Deprecated 307 public MdmLink setGoldenResourcePid(Long theGoldenResourcePid) { 308 setPersonPid(theGoldenResourcePid); 309 310 myGoldenResourcePid = theGoldenResourcePid; 311 return this; 312 } 313 314 public ResourceTable getSource() { 315 return mySource; 316 } 317 318 public MdmLink setSource(ResourceTable theSource) { 319 mySource = theSource; 320 mySourcePid = theSource.getId().getId(); 321 mySourcePartitionId = theSource.getPersistentId().getPartitionId(); 322 return this; 323 } 324 325 @Deprecated 326 public Long getSourcePid() { 327 return mySourcePid; 328 } 329 330 /** 331 * @deprecated Use {@link #setSourcePersistenceId(JpaPid)} instead 332 */ 333 @Deprecated 334 public MdmLink setSourcePid(Long theSourcePid) { 335 mySourcePid = theSourcePid; 336 return this; 337 } 338 339 @Override 340 public MdmMatchResultEnum getMatchResult() { 341 return myMatchResult; 342 } 343 344 @Override 345 public MdmLink setMatchResult(MdmMatchResultEnum theMatchResult) { 346 myMatchResult = theMatchResult; 347 return this; 348 } 349 350 @Override 351 public MdmLinkSourceEnum getLinkSource() { 352 return myLinkSource; 353 } 354 355 @Override 356 public MdmLink setLinkSource(MdmLinkSourceEnum theLinkSource) { 357 myLinkSource = theLinkSource; 358 return this; 359 } 360 361 @Override 362 public Date getCreated() { 363 return myCreated; 364 } 365 366 @Override 367 public MdmLink setCreated(Date theCreated) { 368 myCreated = theCreated; 369 return this; 370 } 371 372 @Override 373 public Date getUpdated() { 374 return myUpdated; 375 } 376 377 @Override 378 public MdmLink setUpdated(Date theUpdated) { 379 myUpdated = theUpdated; 380 return this; 381 } 382 383 @Override 384 public String getVersion() { 385 return myVersion; 386 } 387 388 @Override 389 public MdmLink setVersion(String theVersion) { 390 myVersion = theVersion; 391 return this; 392 } 393 394 @Override 395 public Long getVector() { 396 return myVector; 397 } 398 399 @Override 400 public MdmLink setVector(Long theVector) { 401 myVector = theVector; 402 return this; 403 } 404 405 @Override 406 public Double getScore() { 407 return myScore; 408 } 409 410 @Override 411 public MdmLink setScore(Double theScore) { 412 myScore = theScore; 413 return this; 414 } 415 416 @Override 417 public Boolean getEidMatch() { 418 return myEidMatch; 419 } 420 421 /** 422 * Note that this method can not be called <code>getEidMatch</code> or 423 * <code>isEidMatch</code> because Hibernate Search complains about having 424 * 2 accessors for this property 425 */ 426 @Override 427 public Boolean isEidMatchPresent() { 428 return myEidMatch != null && myEidMatch; 429 } 430 431 @Override 432 public MdmLink setEidMatch(Boolean theEidMatch) { 433 myEidMatch = theEidMatch; 434 return this; 435 } 436 437 @Override 438 public Boolean getHadToCreateNewGoldenResource() { 439 return myHadToCreateNewGoldenResource != null && myHadToCreateNewGoldenResource; 440 } 441 442 @Override 443 public MdmLink setHadToCreateNewGoldenResource(Boolean theHadToCreateNewResource) { 444 myHadToCreateNewGoldenResource = theHadToCreateNewResource; 445 return this; 446 } 447 448 @Override 449 public MdmLink setMdmSourceType(String mdmSourceType) { 450 myMdmSourceType = mdmSourceType; 451 return this; 452 } 453 454 @Override 455 public String toString() { 456 return new ToStringBuilder(this) 457 .append("myId", myId) 458 .append("myGoldenResource", myGoldenResourcePid) 459 .append("mySourcePid", mySourcePid) 460 .append("myMdmSourceType", myMdmSourceType) 461 .append("myMatchResult", myMatchResult) 462 .append("myLinkSource", myLinkSource) 463 .append("myEidMatch", myEidMatch) 464 .append("myHadToCreateNewResource", myHadToCreateNewGoldenResource) 465 .append("myScore", myScore) 466 .append("myRuleCount", myRuleCount) 467 .append("myPartitionId", getPartitionId()) 468 .toString(); 469 } 470 471 @Override 472 public String getMdmSourceType() { 473 return myMdmSourceType; 474 } 475 476 @Override 477 public Long getRuleCount() { 478 return myRuleCount; 479 } 480 481 @Override 482 public MdmLink setRuleCount(Long theRuleCount) { 483 myRuleCount = theRuleCount; 484 return this; 485 } 486}