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