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