001/*
002 * #%L
003 * HAPI FHIR JPA Server
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.entity;
021
022import ca.uhn.fhir.util.ValidateUtil;
023import com.google.common.annotations.VisibleForTesting;
024import jakarta.annotation.Nonnull;
025import jakarta.persistence.Column;
026import jakarta.persistence.Entity;
027import jakarta.persistence.EnumType;
028import jakarta.persistence.Enumerated;
029import jakarta.persistence.FetchType;
030import jakarta.persistence.ForeignKey;
031import jakarta.persistence.GeneratedValue;
032import jakarta.persistence.GenerationType;
033import jakarta.persistence.Id;
034import jakarta.persistence.Index;
035import jakarta.persistence.JoinColumn;
036import jakarta.persistence.Lob;
037import jakarta.persistence.ManyToOne;
038import jakarta.persistence.SequenceGenerator;
039import jakarta.persistence.Table;
040import org.apache.commons.lang3.Validate;
041import org.apache.commons.lang3.builder.EqualsBuilder;
042import org.apache.commons.lang3.builder.HashCodeBuilder;
043import org.apache.commons.lang3.builder.ToStringBuilder;
044import org.apache.commons.lang3.builder.ToStringStyle;
045import org.hibernate.Length;
046import org.hibernate.annotations.JdbcTypeCode;
047import org.hibernate.search.engine.backend.types.Projectable;
048import org.hibernate.search.engine.backend.types.Searchable;
049import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
050import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
051import org.hibernate.type.SqlTypes;
052import org.hibernate.validator.constraints.NotBlank;
053
054import java.io.Serializable;
055import java.nio.charset.StandardCharsets;
056
057import static org.apache.commons.lang3.StringUtils.left;
058import static org.apache.commons.lang3.StringUtils.length;
059
060@Entity
061@Table(
062                name = "TRM_CONCEPT_PROPERTY",
063                uniqueConstraints = {},
064                indexes = {
065                        // must have same name that indexed FK or SchemaMigrationTest complains because H2 sets this index
066                        // automatically
067                        @Index(name = "FK_CONCEPTPROP_CONCEPT", columnList = "CONCEPT_PID", unique = false),
068                        @Index(name = "FK_CONCEPTPROP_CSV", columnList = "CS_VER_PID")
069                })
070public class TermConceptProperty implements Serializable {
071        public static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
072        private static final long serialVersionUID = 1L;
073        public static final int MAX_LENGTH = 500;
074
075        @ManyToOne(fetch = FetchType.LAZY)
076        @JoinColumn(
077                        name = "CONCEPT_PID",
078                        referencedColumnName = "PID",
079                        foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
080        private TermConcept myConcept;
081
082        /**
083         * TODO: Make this non-null
084         *
085         * @since 3.5.0
086         */
087        @ManyToOne(fetch = FetchType.LAZY)
088        @JoinColumn(
089                        name = "CS_VER_PID",
090                        nullable = true,
091                        referencedColumnName = "PID",
092                        foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CSV"))
093        private TermCodeSystemVersion myCodeSystemVersion;
094
095        @Id()
096        @SequenceGenerator(name = "SEQ_CONCEPT_PROP_PID", sequenceName = "SEQ_CONCEPT_PROP_PID")
097        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID")
098        @Column(name = "PID")
099        private Long myId;
100
101        @Column(name = "PROP_KEY", nullable = false, length = MAX_LENGTH)
102        @NotBlank
103        @GenericField(searchable = Searchable.YES)
104        private String myKey;
105
106        @Column(name = "PROP_VAL", nullable = true, length = MAX_LENGTH)
107        @FullTextField(searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
108        @GenericField(name = "myValueString", searchable = Searchable.YES)
109        private String myValue;
110
111        @Deprecated(since = "7.2.0")
112        @Column(name = "PROP_VAL_LOB")
113        @Lob()
114        private byte[] myValueLob;
115
116        @Column(name = "PROP_VAL_BIN", nullable = true, length = Length.LONG32)
117        private byte[] myValueBin;
118
119        @Enumerated(EnumType.ORDINAL)
120        @Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH)
121        @JdbcTypeCode(SqlTypes.INTEGER)
122        private TermConceptPropertyTypeEnum myType;
123
124        /**
125         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
126         */
127        @Column(name = "PROP_CODESYSTEM", length = MAX_LENGTH, nullable = true)
128        private String myCodeSystem;
129
130        /**
131         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
132         */
133        @Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true)
134        @GenericField(name = "myDisplayString", searchable = Searchable.YES)
135        private String myDisplay;
136
137        /**
138         * Constructor
139         */
140        public TermConceptProperty() {
141                super();
142        }
143
144        /**
145         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
146         */
147        public String getCodeSystem() {
148                return myCodeSystem;
149        }
150
151        /**
152         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
153         */
154        public TermConceptProperty setCodeSystem(String theCodeSystem) {
155                ValidateUtil.isNotTooLongOrThrowIllegalArgument(
156                                theCodeSystem,
157                                MAX_LENGTH,
158                                "Property code system exceeds maximum length (" + MAX_LENGTH + "): " + length(theCodeSystem));
159                myCodeSystem = theCodeSystem;
160                return this;
161        }
162
163        /**
164         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
165         */
166        public String getDisplay() {
167                return myDisplay;
168        }
169
170        /**
171         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
172         */
173        public TermConceptProperty setDisplay(String theDisplay) {
174                myDisplay = left(theDisplay, MAX_LENGTH);
175                return this;
176        }
177
178        public String getKey() {
179                return myKey;
180        }
181
182        public TermConceptProperty setKey(@Nonnull String theKey) {
183                ValidateUtil.isNotBlankOrThrowIllegalArgument(theKey, "theKey must not be null or empty");
184                ValidateUtil.isNotTooLongOrThrowIllegalArgument(
185                                theKey, MAX_LENGTH, "Code exceeds maximum length (" + MAX_LENGTH + "): " + length(theKey));
186                myKey = theKey;
187                return this;
188        }
189
190        public TermConceptPropertyTypeEnum getType() {
191                return myType;
192        }
193
194        public TermConceptProperty setType(@Nonnull TermConceptPropertyTypeEnum theType) {
195                Validate.notNull(theType);
196                myType = theType;
197                return this;
198        }
199
200        /**
201         * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string}
202         * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property.
203         */
204        public String getValue() {
205                if (hasValueBin()) {
206                        return getValueBinAsString();
207                }
208                return myValue;
209        }
210
211        /**
212         * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string}
213         * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property.
214         */
215        public TermConceptProperty setValue(String theValue) {
216                if (theValue.length() > MAX_LENGTH) {
217                        setValueBin(theValue);
218                } else {
219                        myValueLob = null;
220                        myValueBin = null;
221                }
222                myValue = left(theValue, MAX_LENGTH);
223                return this;
224        }
225
226        public boolean hasValueBin() {
227                if (myValueBin != null && myValueBin.length > 0) {
228                        return true;
229                }
230
231                if (myValueLob != null && myValueLob.length > 0) {
232                        return true;
233                }
234                return false;
235        }
236
237        public TermConceptProperty setValueBin(byte[] theValueBin) {
238                myValueBin = theValueBin;
239                myValueLob = theValueBin;
240                return this;
241        }
242
243        public TermConceptProperty setValueBin(String theValueBin) {
244                return setValueBin(theValueBin.getBytes(StandardCharsets.UTF_8));
245        }
246
247        public String getValueBinAsString() {
248                if (myValueBin != null && myValueBin.length > 0) {
249                        return new String(myValueBin, StandardCharsets.UTF_8);
250                }
251
252                return new String(myValueLob, StandardCharsets.UTF_8);
253        }
254
255        public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
256                myCodeSystemVersion = theCodeSystemVersion;
257                return this;
258        }
259
260        public TermConceptProperty setConcept(TermConcept theConcept) {
261                myConcept = theConcept;
262                return this;
263        }
264
265        @Override
266        public String toString() {
267                return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
268                                .append("conceptPid", myConcept.getId())
269                                .append("key", myKey)
270                                .append("value", getValue())
271                                .toString();
272        }
273
274        @Override
275        public boolean equals(Object theO) {
276                if (this == theO) {
277                        return true;
278                }
279
280                if (theO == null || getClass() != theO.getClass()) {
281                        return false;
282                }
283
284                TermConceptProperty that = (TermConceptProperty) theO;
285
286                return new EqualsBuilder()
287                                .append(myKey, that.myKey)
288                                .append(myValue, that.myValue)
289                                .append(myType, that.myType)
290                                .append(myCodeSystem, that.myCodeSystem)
291                                .append(myDisplay, that.myDisplay)
292                                .isEquals();
293        }
294
295        @Override
296        public int hashCode() {
297                return new HashCodeBuilder(17, 37)
298                                .append(myKey)
299                                .append(myValue)
300                                .append(myType)
301                                .append(myCodeSystem)
302                                .append(myDisplay)
303                                .toHashCode();
304        }
305
306        public Long getPid() {
307                return myId;
308        }
309
310        @VisibleForTesting
311        public byte[] getValueBlobForTesting() {
312                return myValueLob;
313        }
314
315        @VisibleForTesting
316        public void setValueBlobForTesting(byte[] theValueLob) {
317                myValueLob = theValueLob;
318        }
319
320        @VisibleForTesting
321        public byte[] getValueBinForTesting() {
322                return myValueBin;
323        }
324
325        @VisibleForTesting
326        public void setValueBinForTesting(byte[] theValuebin) {
327                myValueBin = theValuebin;
328        }
329}