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}