001/*
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2025 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.entity;
021
022import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
023import ca.uhn.fhir.jpa.model.entity.IdAndPartitionId;
024import ca.uhn.fhir.util.ValidateUtil;
025import com.google.common.annotations.VisibleForTesting;
026import jakarta.annotation.Nonnull;
027import jakarta.persistence.Column;
028import jakarta.persistence.Entity;
029import jakarta.persistence.FetchType;
030import jakarta.persistence.ForeignKey;
031import jakarta.persistence.GeneratedValue;
032import jakarta.persistence.GenerationType;
033import jakarta.persistence.Id;
034import jakarta.persistence.IdClass;
035import jakarta.persistence.JoinColumn;
036import jakarta.persistence.JoinColumns;
037import jakarta.persistence.Lob;
038import jakarta.persistence.ManyToOne;
039import jakarta.persistence.OneToMany;
040import jakarta.persistence.SequenceGenerator;
041import jakarta.persistence.Table;
042import jakarta.persistence.Transient;
043import jakarta.persistence.UniqueConstraint;
044import org.apache.commons.lang3.builder.EqualsBuilder;
045import org.apache.commons.lang3.builder.HashCodeBuilder;
046import org.apache.commons.lang3.builder.ToStringBuilder;
047import org.apache.commons.lang3.builder.ToStringStyle;
048import org.hibernate.Length;
049
050import java.io.Serializable;
051import java.util.ArrayList;
052import java.util.List;
053
054import static java.util.Objects.nonNull;
055import static org.apache.commons.lang3.StringUtils.isNotEmpty;
056import static org.apache.commons.lang3.StringUtils.left;
057import static org.apache.commons.lang3.StringUtils.length;
058
059/*
060 * DM 2019-08-01 - Do not use IDX_VALUESET_CONCEPT_CS_CD or IDX_VALUESET_CONCEPT_CS_CODE; this was previously used as an index so reusing the name will
061 * bork up migration tasks.
062 */
063@Table(
064                name = "TRM_VALUESET_CONCEPT",
065                uniqueConstraints = {
066                        @UniqueConstraint(
067                                        name = "IDX_VS_CONCEPT_CSCD",
068                                        columnNames = {"PARTITION_ID", "VALUESET_PID", "SYSTEM_URL", "CODEVAL"}),
069                        @UniqueConstraint(
070                                        name = "IDX_VS_CONCEPT_ORDER",
071                                        columnNames = {"PARTITION_ID", "VALUESET_PID", "VALUESET_ORDER"})
072                })
073@Entity()
074@IdClass(IdAndPartitionId.class)
075public class TermValueSetConcept extends BasePartitionable implements Serializable {
076        private static final long serialVersionUID = 1L;
077
078        @Id()
079        @SequenceGenerator(name = "SEQ_VALUESET_CONCEPT_PID", sequenceName = "SEQ_VALUESET_CONCEPT_PID")
080        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_CONCEPT_PID")
081        @Column(name = "PID")
082        private Long myId;
083
084        @ManyToOne(fetch = FetchType.LAZY)
085        @JoinColumns(
086                        value = {
087                                @JoinColumn(
088                                                name = "VALUESET_PID",
089                                                referencedColumnName = "PID",
090                                                insertable = true,
091                                                updatable = false,
092                                                nullable = false),
093                                @JoinColumn(
094                                                name = "PARTITION_ID",
095                                                referencedColumnName = "PARTITION_ID",
096                                                insertable = true,
097                                                updatable = false,
098                                                nullable = false)
099                        },
100                        foreignKey = @ForeignKey(name = "FK_TRM_VALUESET_PID"))
101        private TermValueSet myValueSet;
102
103        @Column(name = "VALUESET_PID", insertable = false, updatable = false, nullable = false)
104        private Long myValueSetPid;
105
106        @Column(name = "INDEX_STATUS", nullable = true)
107        private Long myIndexStatus;
108
109        @Column(name = "VALUESET_ORDER", nullable = false)
110        private int myOrder;
111
112        @Transient
113        private String myValueSetUrl;
114
115        @Transient
116        private String myValueSetName;
117
118        @Column(name = "SOURCE_PID", nullable = true)
119        private Long mySourceConceptPid;
120
121        @Deprecated(since = "7.2.0")
122        @Lob
123        @Column(name = "SOURCE_DIRECT_PARENT_PIDS", nullable = true)
124        private String mySourceConceptDirectParentPids;
125
126        @Column(name = "SOURCE_DIRECT_PARENT_PIDS_VC", nullable = true, length = Length.LONG32)
127        private String mySourceConceptDirectParentPidsVc;
128
129        @Column(name = "SYSTEM_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH)
130        private String mySystem;
131
132        @Column(name = "SYSTEM_VER", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
133        private String mySystemVer;
134
135        @Column(name = "CODEVAL", nullable = false, length = TermConcept.MAX_CODE_LENGTH)
136        private String myCode;
137
138        @Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH)
139        private String myDisplay;
140
141        @OneToMany(mappedBy = "myConcept", fetch = FetchType.LAZY)
142        private List<TermValueSetConceptDesignation> myDesignations;
143
144        @Transient
145        private transient Integer myHashCode;
146
147        /**
148         * Constructor
149         */
150        public TermValueSetConcept() {
151                super();
152        }
153
154        public Long getId() {
155                return myId;
156        }
157
158        public IdAndPartitionId getPartitionedId() {
159                return IdAndPartitionId.forId(myId, this);
160        }
161
162        public TermValueSet getValueSet() {
163                return myValueSet;
164        }
165
166        public TermValueSetConcept setValueSet(TermValueSet theValueSet) {
167                myValueSet = theValueSet;
168                setPartitionId(theValueSet.getPartitionId());
169                return this;
170        }
171
172        public int getOrder() {
173                return myOrder;
174        }
175
176        public TermValueSetConcept setOrder(int theOrder) {
177                myOrder = theOrder;
178                return this;
179        }
180
181        public String getValueSetUrl() {
182                if (myValueSetUrl == null) {
183                        myValueSetUrl = getValueSet().getUrl();
184                }
185
186                return myValueSetUrl;
187        }
188
189        public String getValueSetName() {
190                if (myValueSetName == null) {
191                        myValueSetName = getValueSet().getName();
192                }
193
194                return myValueSetName;
195        }
196
197        public String getSystem() {
198                return mySystem;
199        }
200
201        public TermValueSetConcept setSystem(@Nonnull String theSystem) {
202                ValidateUtil.isNotBlankOrThrowIllegalArgument(theSystem, "theSystem must not be null or empty");
203                ValidateUtil.isNotTooLongOrThrowIllegalArgument(
204                                theSystem,
205                                TermCodeSystem.MAX_URL_LENGTH,
206                                "System exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSystem));
207                mySystem = theSystem;
208                return this;
209        }
210
211        public String getSystemVersion() {
212                return mySystemVer;
213        }
214
215        public TermValueSetConcept setSystemVersion(String theSystemVersion) {
216                ValidateUtil.isNotTooLongOrThrowIllegalArgument(
217                                theSystemVersion,
218                                TermCodeSystemVersion.MAX_VERSION_LENGTH,
219                                "System version exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): "
220                                                + length(theSystemVersion));
221                mySystemVer = theSystemVersion;
222                return this;
223        }
224
225        public String getCode() {
226                return myCode;
227        }
228
229        public TermValueSetConcept setCode(@Nonnull String theCode) {
230                ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty");
231                ValidateUtil.isNotTooLongOrThrowIllegalArgument(
232                                theCode,
233                                TermConcept.MAX_CODE_LENGTH,
234                                "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode));
235                myCode = theCode;
236                return this;
237        }
238
239        public String getDisplay() {
240                return myDisplay;
241        }
242
243        public TermValueSetConcept setDisplay(String theDisplay) {
244                myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH);
245                return this;
246        }
247
248        public List<TermValueSetConceptDesignation> getDesignations() {
249                if (myDesignations == null) {
250                        myDesignations = new ArrayList<>();
251                }
252
253                return myDesignations;
254        }
255
256        @Override
257        public boolean equals(Object theO) {
258                if (this == theO) return true;
259
260                if (!(theO instanceof TermValueSetConcept)) return false;
261
262                TermValueSetConcept that = (TermValueSetConcept) theO;
263
264                return new EqualsBuilder()
265                                .append(myValueSetPid, that.myValueSetPid)
266                                .append(getSystem(), that.getSystem())
267                                .append(getCode(), that.getCode())
268                                .isEquals();
269        }
270
271        @Override
272        public int hashCode() {
273                if (myHashCode == null) {
274                        myHashCode = new HashCodeBuilder(17, 37)
275                                        .append(myValueSetPid)
276                                        .append(getSystem())
277                                        .append(getCode())
278                                        .toHashCode();
279                }
280                return myHashCode;
281        }
282
283        @Override
284        public String toString() {
285                return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
286                                .append("id", myId)
287                                .append("order", myOrder)
288                                .append("system", mySystem)
289                                .append("code", myCode)
290                                .append("valueSet", myValueSet != null ? myValueSet.getId() : "(null)")
291                                .append("valueSetPid", myValueSetPid)
292                                .append("valueSetUrl", this.getValueSetUrl())
293                                .append("valueSetName", this.getValueSetName())
294                                .append("display", myDisplay)
295                                .append("designationCount", myDesignations != null ? myDesignations.size() : "(null)")
296                                .append("parentPids", getSourceConceptDirectParentPids())
297                                .toString();
298        }
299
300        public Long getIndexStatus() {
301                return myIndexStatus;
302        }
303
304        public void setIndexStatus(Long theIndexStatus) {
305                myIndexStatus = theIndexStatus;
306        }
307
308        public void setSourceConceptPid(Long theSourceConceptPid) {
309                mySourceConceptPid = theSourceConceptPid;
310        }
311
312        public void setSourceConceptDirectParentPids(String theSourceConceptDirectParentPids) {
313                mySourceConceptDirectParentPids = theSourceConceptDirectParentPids;
314                mySourceConceptDirectParentPidsVc = theSourceConceptDirectParentPids;
315        }
316
317        public String getSourceConceptDirectParentPids() {
318                return isNotEmpty(mySourceConceptDirectParentPidsVc)
319                                ? mySourceConceptDirectParentPidsVc
320                                : mySourceConceptDirectParentPids;
321        }
322
323        public void clearSourceConceptDirectParentPidsLob() {
324                mySourceConceptDirectParentPids = null;
325        }
326
327        @VisibleForTesting
328        public boolean hasSourceConceptDirectParentPidsLob() {
329                return nonNull(mySourceConceptDirectParentPids);
330        }
331}