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}