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}