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