001/*
002 * #%L
003 * HAPI FHIR JPA Model
004 * %%
005 * Copyright (C) 2014 - 2024 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 jakarta.persistence.AttributeOverride;
023import jakarta.persistence.Column;
024import jakarta.persistence.Embedded;
025import jakarta.persistence.Entity;
026import jakarta.persistence.FetchType;
027import jakarta.persistence.ForeignKey;
028import jakarta.persistence.GeneratedValue;
029import jakarta.persistence.GenerationType;
030import jakarta.persistence.Id;
031import jakarta.persistence.Index;
032import jakarta.persistence.JoinColumn;
033import jakarta.persistence.ManyToOne;
034import jakarta.persistence.SequenceGenerator;
035import jakarta.persistence.Table;
036import jakarta.persistence.Temporal;
037import jakarta.persistence.TemporalType;
038import jakarta.persistence.Transient;
039import org.apache.commons.lang3.Validate;
040import org.apache.commons.lang3.builder.EqualsBuilder;
041import org.apache.commons.lang3.builder.HashCodeBuilder;
042import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
043import org.hl7.fhir.instance.model.api.IIdType;
044
045import java.util.Date;
046
047@Entity
048@Table(
049                name = "HFJ_RES_LINK",
050                indexes = {
051                        // We need to join both ways, so index from src->tgt and from tgt->src.
052                        // From src->tgt, rows are usually written all together as part of ingestion - keep the index small, and
053                        // read blocks as needed.
054                        @Index(name = "IDX_RL_SRC", columnList = "SRC_RESOURCE_ID"),
055                        // But from tgt->src, include all the match columns. Targets will usually be randomly distributed - each row
056                        // in separate block.
057                        @Index(
058                                        name = "IDX_RL_TGT_v2",
059                                        columnList = "TARGET_RESOURCE_ID, SRC_PATH, SRC_RESOURCE_ID, TARGET_RESOURCE_TYPE,PARTITION_ID")
060                })
061public class ResourceLink extends BaseResourceIndex {
062
063        public static final int SRC_PATH_LENGTH = 500;
064        private static final long serialVersionUID = 1L;
065
066        @SequenceGenerator(name = "SEQ_RESLINK_ID", sequenceName = "SEQ_RESLINK_ID")
067        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESLINK_ID")
068        @Id
069        @Column(name = "PID")
070        private Long myId;
071
072        @Column(name = "SRC_PATH", length = SRC_PATH_LENGTH, nullable = false)
073        private String mySourcePath;
074
075        @ManyToOne(optional = false, fetch = FetchType.LAZY)
076        @JoinColumn(
077                        name = "SRC_RESOURCE_ID",
078                        referencedColumnName = "RES_ID",
079                        nullable = false,
080                        foreignKey = @ForeignKey(name = "FK_RESLINK_SOURCE"))
081        private ResourceTable mySourceResource;
082
083        @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false)
084        private Long mySourceResourcePid;
085
086        @Column(name = "SOURCE_RESOURCE_TYPE", updatable = false, nullable = false, length = ResourceTable.RESTYPE_LEN)
087        @FullTextField
088        private String mySourceResourceType;
089
090        @ManyToOne(optional = true, fetch = FetchType.LAZY)
091        @JoinColumn(
092                        name = "TARGET_RESOURCE_ID",
093                        referencedColumnName = "RES_ID",
094                        nullable = true,
095                        insertable = false,
096                        updatable = false,
097                        foreignKey = @ForeignKey(name = "FK_RESLINK_TARGET"))
098        private ResourceTable myTargetResource;
099
100        @Column(name = "TARGET_RESOURCE_ID", insertable = true, updatable = true, nullable = true)
101        @FullTextField
102        private Long myTargetResourcePid;
103
104        @Column(name = "TARGET_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN)
105        @FullTextField
106        private String myTargetResourceType;
107
108        @Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
109        @FullTextField
110        private String myTargetResourceUrl;
111
112        @Column(name = "TARGET_RESOURCE_VERSION", nullable = true)
113        private Long myTargetResourceVersion;
114
115        @FullTextField
116        @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
117        @Temporal(TemporalType.TIMESTAMP)
118        private Date myUpdated;
119
120        @Transient
121        private transient String myTargetResourceId;
122
123        @Embedded
124        @AttributeOverride(name = "myPartitionId", column = @Column(name = "TARGET_RES_PARTITION_ID"))
125        @AttributeOverride(name = "myPartitionDate", column = @Column(name = "TARGET_RES_PARTITION_DATE"))
126        private PartitionablePartitionId myTargetResourcePartitionId;
127
128        /**
129         * Constructor
130         */
131        public ResourceLink() {
132                super();
133        }
134
135        public Long getTargetResourceVersion() {
136                return myTargetResourceVersion;
137        }
138
139        public void setTargetResourceVersion(Long theTargetResourceVersion) {
140                myTargetResourceVersion = theTargetResourceVersion;
141        }
142
143        public String getTargetResourceId() {
144                if (myTargetResourceId == null && myTargetResource != null) {
145                        myTargetResourceId = getTargetResource().getIdDt().getIdPart();
146                }
147                return myTargetResourceId;
148        }
149
150        public String getSourceResourceType() {
151                return mySourceResourceType;
152        }
153
154        public String getTargetResourceType() {
155                return myTargetResourceType;
156        }
157
158        @Override
159        public boolean equals(Object theObj) {
160                if (this == theObj) {
161                        return true;
162                }
163                if (theObj == null) {
164                        return false;
165                }
166                if (!(theObj instanceof ResourceLink)) {
167                        return false;
168                }
169                ResourceLink obj = (ResourceLink) theObj;
170                EqualsBuilder b = new EqualsBuilder();
171                b.append(mySourcePath, obj.mySourcePath);
172                b.append(mySourceResource, obj.mySourceResource);
173                b.append(myTargetResourceUrl, obj.myTargetResourceUrl);
174                b.append(myTargetResourceType, obj.myTargetResourceType);
175                b.append(myTargetResourceVersion, obj.myTargetResourceVersion);
176                // In cases where we are extracting links from a resource that has not yet been persisted, the target resource
177                // pid
178                // will be null so we use the target resource id to differentiate instead
179                if (getTargetResourcePid() == null) {
180                        b.append(getTargetResourceId(), obj.getTargetResourceId());
181                } else {
182                        b.append(getTargetResourcePid(), obj.getTargetResourcePid());
183                }
184                return b.isEquals();
185        }
186
187        @Override
188        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
189                ResourceLink source = (ResourceLink) theSource;
190                mySourcePath = source.getSourcePath();
191                myTargetResource = source.getTargetResource();
192                myTargetResourceId = source.getTargetResourceId();
193                myTargetResourcePid = source.getTargetResourcePid();
194                myTargetResourceType = source.getTargetResourceType();
195                myTargetResourceVersion = source.getTargetResourceVersion();
196                myTargetResourceUrl = source.getTargetResourceUrl();
197                myTargetResourcePartitionId = source.getTargetResourcePartitionId();
198        }
199
200        public String getSourcePath() {
201                return mySourcePath;
202        }
203
204        public void setSourcePath(String theSourcePath) {
205                mySourcePath = theSourcePath;
206        }
207
208        public Long getSourceResourcePid() {
209                return mySourceResourcePid;
210        }
211
212        public ResourceTable getSourceResource() {
213                return mySourceResource;
214        }
215
216        public void setSourceResource(ResourceTable theSourceResource) {
217                mySourceResource = theSourceResource;
218                mySourceResourcePid = theSourceResource.getId();
219                mySourceResourceType = theSourceResource.getResourceType();
220        }
221
222        public void setTargetResource(String theResourceType, Long theResourcePid, String theTargetResourceId) {
223                Validate.notBlank(theResourceType);
224
225                myTargetResourceType = theResourceType;
226                myTargetResourcePid = theResourcePid;
227                myTargetResourceId = theTargetResourceId;
228        }
229
230        public String getTargetResourceUrl() {
231                return myTargetResourceUrl;
232        }
233
234        public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
235                Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
236                Validate.isTrue(theTargetResourceUrl.hasResourceType());
237
238                //              if (theTargetResourceUrl.hasIdPart()) {
239                // do nothing
240                //              } else {
241                // Must have set an url like http://example.org/something
242                // We treat 'something' as the resource type because of fix for #659. Prior to #659 fix, 'something' was
243                // treated as the id and 'example.org' was treated as the resource type
244                // Maybe log a warning?
245                //              }
246
247                myTargetResourceType = theTargetResourceUrl.getResourceType();
248                myTargetResourceUrl = theTargetResourceUrl.getValue();
249        }
250
251        public Long getTargetResourcePid() {
252                return myTargetResourcePid;
253        }
254
255        public void setTargetResourceUrlCanonical(String theTargetResourceUrl) {
256                Validate.notBlank(theTargetResourceUrl);
257
258                myTargetResourceType = "(unknown)";
259                myTargetResourceUrl = theTargetResourceUrl;
260        }
261
262        public Date getUpdated() {
263                return myUpdated;
264        }
265
266        public void setUpdated(Date theUpdated) {
267                myUpdated = theUpdated;
268        }
269
270        @Override
271        public Long getId() {
272                return myId;
273        }
274
275        @Override
276        public void setId(Long theId) {
277                myId = theId;
278        }
279
280        public PartitionablePartitionId getTargetResourcePartitionId() {
281                return myTargetResourcePartitionId;
282        }
283
284        public ResourceLink setTargetResourcePartitionId(PartitionablePartitionId theTargetResourcePartitionId) {
285                myTargetResourcePartitionId = theTargetResourcePartitionId;
286                return this;
287        }
288
289        @Override
290        public void clearHashes() {
291                // nothing right now
292        }
293
294        @Override
295        public void calculateHashes() {
296                // nothing right now
297        }
298
299        @Override
300        public int hashCode() {
301                HashCodeBuilder b = new HashCodeBuilder();
302                b.append(mySourcePath);
303                b.append(mySourceResource);
304                b.append(myTargetResourceUrl);
305                b.append(myTargetResourceVersion);
306
307                // In cases where we are extracting links from a resource that has not yet been persisted, the target resource
308                // pid
309                // will be null so we use the target resource id to differentiate instead
310                if (getTargetResourcePid() == null) {
311                        b.append(getTargetResourceId());
312                } else {
313                        b.append(getTargetResourcePid());
314                }
315                return b.toHashCode();
316        }
317
318        @Override
319        public String toString() {
320                StringBuilder b = new StringBuilder();
321                b.append("ResourceLink[");
322                b.append("path=").append(mySourcePath);
323                b.append(", srcResId=").append(mySourceResourcePid);
324                b.append(", targetResId=").append(myTargetResourcePid);
325                b.append(", targetResType=").append(myTargetResourceType);
326                b.append(", targetResVersion=").append(myTargetResourceVersion);
327                b.append(", targetResUrl=").append(myTargetResourceUrl);
328
329                b.append("]");
330                return b.toString();
331        }
332
333        public ResourceTable getTargetResource() {
334                return myTargetResource;
335        }
336
337        /**
338         * Creates a clone of this resourcelink which doesn't contain the internal PID
339         * of the target resource.
340         */
341        public ResourceLink cloneWithoutTargetPid() {
342                ResourceLink retVal = new ResourceLink();
343                retVal.mySourceResource = mySourceResource;
344                retVal.mySourceResourcePid = mySourceResource.getId();
345                retVal.mySourceResourceType = mySourceResource.getResourceType();
346                retVal.mySourcePath = mySourcePath;
347                retVal.myUpdated = myUpdated;
348                retVal.myTargetResourceType = myTargetResourceType;
349                if (myTargetResourceId != null) {
350                        retVal.myTargetResourceId = myTargetResourceId;
351                } else if (myTargetResource != null) {
352                        retVal.myTargetResourceId = myTargetResource.getIdDt().getIdPart();
353                }
354                retVal.myTargetResourceUrl = myTargetResourceUrl;
355                retVal.myTargetResourceVersion = myTargetResourceVersion;
356                return retVal;
357        }
358
359        public static ResourceLink forAbsoluteReference(
360                        String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) {
361                ResourceLink retVal = new ResourceLink();
362                retVal.setSourcePath(theSourcePath);
363                retVal.setSourceResource(theSourceResource);
364                retVal.setTargetResourceUrl(theTargetResourceUrl);
365                retVal.setUpdated(theUpdated);
366                return retVal;
367        }
368
369        /**
370         * Factory for canonical URL
371         */
372        public static ResourceLink forLogicalReference(
373                        String theSourcePath, ResourceTable theSourceResource, String theTargetResourceUrl, Date theUpdated) {
374                ResourceLink retVal = new ResourceLink();
375                retVal.setSourcePath(theSourcePath);
376                retVal.setSourceResource(theSourceResource);
377                retVal.setTargetResourceUrlCanonical(theTargetResourceUrl);
378                retVal.setUpdated(theUpdated);
379                return retVal;
380        }
381
382        public static ResourceLink forLocalReference(
383                        ResourceLinkForLocalReferenceParams theResourceLinkForLocalReferenceParams) {
384
385                ResourceLink retVal = new ResourceLink();
386                retVal.setSourcePath(theResourceLinkForLocalReferenceParams.getSourcePath());
387                retVal.setSourceResource(theResourceLinkForLocalReferenceParams.getSourceResource());
388                retVal.setTargetResource(
389                                theResourceLinkForLocalReferenceParams.getTargetResourceType(),
390                                theResourceLinkForLocalReferenceParams.getTargetResourcePid(),
391                                theResourceLinkForLocalReferenceParams.getTargetResourceId());
392
393                retVal.setTargetResourcePartitionId(
394                                theResourceLinkForLocalReferenceParams.getTargetResourcePartitionablePartitionId());
395                retVal.setTargetResourceVersion(theResourceLinkForLocalReferenceParams.getTargetResourceVersion());
396                retVal.setUpdated(theResourceLinkForLocalReferenceParams.getUpdated());
397
398                return retVal;
399        }
400
401        public static class ResourceLinkForLocalReferenceParams {
402                private String mySourcePath;
403                private ResourceTable mySourceResource;
404                private String myTargetResourceType;
405                private Long myTargetResourcePid;
406                private String myTargetResourceId;
407                private Date myUpdated;
408                private Long myTargetResourceVersion;
409                private PartitionablePartitionId myTargetResourcePartitionablePartitionId;
410
411                public static ResourceLinkForLocalReferenceParams instance() {
412                        return new ResourceLinkForLocalReferenceParams();
413                }
414
415                public String getSourcePath() {
416                        return mySourcePath;
417                }
418
419                public ResourceLinkForLocalReferenceParams setSourcePath(String theSourcePath) {
420                        mySourcePath = theSourcePath;
421                        return this;
422                }
423
424                public ResourceTable getSourceResource() {
425                        return mySourceResource;
426                }
427
428                public ResourceLinkForLocalReferenceParams setSourceResource(ResourceTable theSourceResource) {
429                        mySourceResource = theSourceResource;
430                        return this;
431                }
432
433                public String getTargetResourceType() {
434                        return myTargetResourceType;
435                }
436
437                public ResourceLinkForLocalReferenceParams setTargetResourceType(String theTargetResourceType) {
438                        myTargetResourceType = theTargetResourceType;
439                        return this;
440                }
441
442                public Long getTargetResourcePid() {
443                        return myTargetResourcePid;
444                }
445
446                public ResourceLinkForLocalReferenceParams setTargetResourcePid(Long theTargetResourcePid) {
447                        myTargetResourcePid = theTargetResourcePid;
448                        return this;
449                }
450
451                public String getTargetResourceId() {
452                        return myTargetResourceId;
453                }
454
455                public ResourceLinkForLocalReferenceParams setTargetResourceId(String theTargetResourceId) {
456                        myTargetResourceId = theTargetResourceId;
457                        return this;
458                }
459
460                public Date getUpdated() {
461                        return myUpdated;
462                }
463
464                public ResourceLinkForLocalReferenceParams setUpdated(Date theUpdated) {
465                        myUpdated = theUpdated;
466                        return this;
467                }
468
469                public Long getTargetResourceVersion() {
470                        return myTargetResourceVersion;
471                }
472
473                /**
474                 * @param theTargetResourceVersion This should only be populated if the reference actually had a version
475                 */
476                public ResourceLinkForLocalReferenceParams setTargetResourceVersion(Long theTargetResourceVersion) {
477                        myTargetResourceVersion = theTargetResourceVersion;
478                        return this;
479                }
480
481                public PartitionablePartitionId getTargetResourcePartitionablePartitionId() {
482                        return myTargetResourcePartitionablePartitionId;
483                }
484
485                public ResourceLinkForLocalReferenceParams setTargetResourcePartitionablePartitionId(
486                                PartitionablePartitionId theTargetResourcePartitionablePartitionId) {
487                        myTargetResourcePartitionablePartitionId = theTargetResourcePartitionablePartitionId;
488                        return this;
489                }
490        }
491}