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.Table; 035import jakarta.persistence.Temporal; 036import jakarta.persistence.TemporalType; 037import jakarta.persistence.Transient; 038import org.apache.commons.lang3.Validate; 039import org.apache.commons.lang3.builder.EqualsBuilder; 040import org.apache.commons.lang3.builder.HashCodeBuilder; 041import org.hibernate.annotations.GenericGenerator; 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 @GenericGenerator(name = "SEQ_RESLINK_ID", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 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}