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 ca.uhn.fhir.jpa.model.dao.JpaPid; 023import ca.uhn.fhir.model.primitive.IdDt; 024import ca.uhn.fhir.rest.api.Constants; 025import jakarta.persistence.CascadeType; 026import jakarta.persistence.Column; 027import jakarta.persistence.Entity; 028import jakarta.persistence.EnumType; 029import jakarta.persistence.Enumerated; 030import jakarta.persistence.FetchType; 031import jakarta.persistence.ForeignKey; 032import jakarta.persistence.GeneratedValue; 033import jakarta.persistence.GenerationType; 034import jakarta.persistence.Id; 035import jakarta.persistence.Index; 036import jakarta.persistence.JoinColumn; 037import jakarta.persistence.Lob; 038import jakarta.persistence.ManyToOne; 039import jakarta.persistence.OneToMany; 040import jakarta.persistence.OneToOne; 041import jakarta.persistence.Table; 042import jakarta.persistence.Transient; 043import jakarta.persistence.UniqueConstraint; 044import org.apache.commons.lang3.builder.ToStringBuilder; 045import org.apache.commons.lang3.builder.ToStringStyle; 046import org.hibernate.Length; 047import org.hibernate.annotations.GenericGenerator; 048import org.hibernate.annotations.OptimisticLock; 049 050import java.io.Serializable; 051import java.util.ArrayList; 052import java.util.Collection; 053 054@Entity 055@Table( 056 name = ResourceHistoryTable.HFJ_RES_VER, 057 uniqueConstraints = { 058 @UniqueConstraint( 059 name = ResourceHistoryTable.IDX_RESVER_ID_VER, 060 columnNames = {"RES_ID", "RES_VER"}) 061 }, 062 indexes = { 063 @Index(name = "IDX_RESVER_TYPE_DATE", columnList = "RES_TYPE,RES_UPDATED,RES_ID"), 064 @Index(name = "IDX_RESVER_ID_DATE", columnList = "RES_ID,RES_UPDATED"), 065 @Index(name = "IDX_RESVER_DATE", columnList = "RES_UPDATED,RES_ID") 066 }) 067public class ResourceHistoryTable extends BaseHasResource implements Serializable { 068 public static final String IDX_RESVER_ID_VER = "IDX_RESVER_ID_VER"; 069 public static final int SOURCE_URI_LENGTH = 100; 070 /** 071 * @see ResourceEncodingEnum 072 */ 073 // Don't reduce the visibility here, we reference this from Smile 074 @SuppressWarnings("WeakerAccess") 075 public static final int ENCODING_COL_LENGTH = 5; 076 077 public static final String HFJ_RES_VER = "HFJ_RES_VER"; 078 private static final long serialVersionUID = 1L; 079 080 @Id 081 @GenericGenerator( 082 name = "SEQ_RESOURCE_HISTORY_ID", 083 type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 084 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_HISTORY_ID") 085 @Column(name = "PID") 086 private Long myId; 087 088 @ManyToOne(fetch = FetchType.LAZY) 089 @JoinColumn( 090 name = "RES_ID", 091 nullable = false, 092 updatable = false, 093 foreignKey = @ForeignKey(name = "FK_RESOURCE_HISTORY_RESOURCE")) 094 private ResourceTable myResourceTable; 095 096 @Column(name = "RES_ID", nullable = false, updatable = false, insertable = false) 097 private Long myResourceId; 098 099 @Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN, nullable = false) 100 private String myResourceType; 101 102 @Column(name = "RES_VER", nullable = false) 103 private Long myResourceVersion; 104 105 @OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) 106 private Collection<ResourceHistoryTag> myTags; 107 108 @Column(name = "RES_TEXT", length = Integer.MAX_VALUE - 1, nullable = true) 109 @Lob() 110 @OptimisticLock(excluded = true) 111 private byte[] myResource; 112 113 @Column(name = "RES_TEXT_VC", length = Length.LONG32, nullable = true) 114 @OptimisticLock(excluded = true) 115 private String myResourceTextVc; 116 117 @Column(name = "RES_ENCODING", nullable = false, length = ENCODING_COL_LENGTH) 118 @Enumerated(EnumType.STRING) 119 @OptimisticLock(excluded = true) 120 private ResourceEncodingEnum myEncoding; 121 122 @OneToOne( 123 mappedBy = "myResourceHistoryTable", 124 cascade = {CascadeType.REMOVE}) 125 private ResourceHistoryProvenanceEntity myProvenance; 126 // TODO: This was added in 6.8.0 - In the future we should drop ResourceHistoryProvenanceEntity 127 @Column(name = "SOURCE_URI", length = SOURCE_URI_LENGTH, nullable = true) 128 private String mySourceUri; 129 // TODO: This was added in 6.8.0 - In the future we should drop ResourceHistoryProvenanceEntity 130 @Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true) 131 private String myRequestId; 132 133 @Transient 134 private transient ResourceHistoryProvenanceEntity myNewHistoryProvenanceEntity; 135 /** 136 * This is stored as an optimization to avoid needing to fetch ResourceTable 137 * to access the resource id. 138 */ 139 @Transient 140 private transient String myTransientForcedId; 141 142 /** 143 * Constructor 144 */ 145 public ResourceHistoryTable() { 146 super(); 147 } 148 149 public String getSourceUri() { 150 return mySourceUri; 151 } 152 153 public void setSourceUri(String theSourceUri) { 154 mySourceUri = theSourceUri; 155 } 156 157 public String getRequestId() { 158 return myRequestId; 159 } 160 161 public void setRequestId(String theRequestId) { 162 myRequestId = theRequestId; 163 } 164 165 @Override 166 public String toString() { 167 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 168 .append("resourceId", myResourceId) 169 .append("resourceType", myResourceType) 170 .append("resourceVersion", myResourceVersion) 171 .append("pid", myId) 172 .toString(); 173 } 174 175 public String getResourceTextVc() { 176 return myResourceTextVc; 177 } 178 179 public void setResourceTextVc(String theResourceTextVc) { 180 myResourceTextVc = theResourceTextVc; 181 } 182 183 public ResourceHistoryProvenanceEntity getProvenance() { 184 return myProvenance; 185 } 186 187 public void addTag(ResourceTag theTag) { 188 ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag(), getPartitionId()); 189 tag.setResourceType(theTag.getResourceType()); 190 getTags().add(tag); 191 } 192 193 @Override 194 public ResourceHistoryTag addTag(TagDefinition theTag) { 195 for (ResourceHistoryTag next : getTags()) { 196 if (next.getTag().equals(theTag)) { 197 return next; 198 } 199 } 200 ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag, getPartitionId()); 201 getTags().add(historyTag); 202 return historyTag; 203 } 204 205 public ResourceEncodingEnum getEncoding() { 206 return myEncoding; 207 } 208 209 public void setEncoding(ResourceEncodingEnum theEncoding) { 210 myEncoding = theEncoding; 211 } 212 213 @Override 214 public Long getId() { 215 return myId; 216 } 217 218 /** 219 * Do not delete, required for java bean introspection 220 */ 221 public Long getMyId() { 222 return myId; 223 } 224 225 /** 226 * Do not delete, required for java bean introspection 227 */ 228 public void setMyId(Long theId) { 229 myId = theId; 230 } 231 232 public byte[] getResource() { 233 return myResource; 234 } 235 236 public void setResource(byte[] theResource) { 237 myResource = theResource; 238 } 239 240 @Override 241 public Long getResourceId() { 242 return myResourceId; 243 } 244 245 public void setResourceId(Long theResourceId) { 246 myResourceId = theResourceId; 247 } 248 249 @Override 250 public String getResourceType() { 251 return myResourceType; 252 } 253 254 public void setResourceType(String theResourceType) { 255 myResourceType = theResourceType; 256 } 257 258 @Override 259 public Collection<ResourceHistoryTag> getTags() { 260 if (myTags == null) { 261 myTags = new ArrayList<>(); 262 } 263 return myTags; 264 } 265 266 @Override 267 public long getVersion() { 268 return myResourceVersion; 269 } 270 271 public void setVersion(long theVersion) { 272 myResourceVersion = theVersion; 273 } 274 275 @Override 276 public boolean isDeleted() { 277 return getDeleted() != null; 278 } 279 280 @Override 281 public void setNotDeleted() { 282 setDeleted(null); 283 } 284 285 @Override 286 public JpaPid getPersistentId() { 287 return JpaPid.fromId(myResourceId); 288 } 289 290 public ResourceTable getResourceTable() { 291 return myResourceTable; 292 } 293 294 public void setResourceTable(ResourceTable theResourceTable) { 295 myResourceTable = theResourceTable; 296 } 297 298 @Override 299 public IdDt getIdDt() { 300 // Avoid a join query if possible 301 String resourceIdPart; 302 if (getTransientForcedId() != null) { 303 resourceIdPart = getTransientForcedId(); 304 } else { 305 resourceIdPart = getResourceTable().getFhirId(); 306 } 307 return new IdDt(getResourceType() + '/' + resourceIdPart + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); 308 } 309 310 /** 311 * Returns <code>true</code> if there is a populated resource text (i.e. 312 * either {@link #getResource()} or {@link #getResourceTextVc()} return a non null 313 * value. 314 */ 315 public boolean hasResource() { 316 return myResource != null || myResourceTextVc != null; 317 } 318 319 /** 320 * This method creates a new HistoryProvenance entity, or might reuse the current one if we've 321 * already created one in the current transaction. This is because we can only increment 322 * the version once in a DB transaction (since hibernate manages that number) so creating 323 * multiple {@link ResourceHistoryProvenanceEntity} entities will result in a constraint error. 324 */ 325 public ResourceHistoryProvenanceEntity toProvenance() { 326 if (myNewHistoryProvenanceEntity == null) { 327 myNewHistoryProvenanceEntity = new ResourceHistoryProvenanceEntity(); 328 } 329 return myNewHistoryProvenanceEntity; 330 } 331 332 public String getTransientForcedId() { 333 return myTransientForcedId; 334 } 335 336 public void setTransientForcedId(String theTransientForcedId) { 337 myTransientForcedId = theTransientForcedId; 338 } 339}