001package ca.uhn.fhir.jpa.entity;
002
003/*
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.util.ValidateUtil;
024import org.apache.commons.lang3.Validate;
025import org.apache.commons.lang3.builder.EqualsBuilder;
026import org.apache.commons.lang3.builder.HashCodeBuilder;
027import org.apache.commons.lang3.builder.ToStringBuilder;
028import org.apache.commons.lang3.builder.ToStringStyle;
029import org.hibernate.search.engine.backend.types.Projectable;
030import org.hibernate.search.engine.backend.types.Searchable;
031import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
032import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
033import org.hibernate.validator.constraints.NotBlank;
034
035import javax.annotation.Nonnull;
036import javax.persistence.Column;
037import javax.persistence.Entity;
038import javax.persistence.FetchType;
039import javax.persistence.ForeignKey;
040import javax.persistence.GeneratedValue;
041import javax.persistence.GenerationType;
042import javax.persistence.Id;
043import javax.persistence.Index;
044import javax.persistence.JoinColumn;
045import javax.persistence.Lob;
046import javax.persistence.ManyToOne;
047import javax.persistence.SequenceGenerator;
048import javax.persistence.Table;
049import java.io.Serializable;
050import java.nio.charset.StandardCharsets;
051
052import static org.apache.commons.lang3.StringUtils.left;
053import static org.apache.commons.lang3.StringUtils.length;
054
055@Entity
056@Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = { }, indexes = {
057        // must have same name that indexed FK or SchemaMigrationTest complains because H2 sets this index automatically
058        @Index(name = "FK_CONCEPTPROP_CONCEPT",  columnList = "CONCEPT_PID", unique = false)
059})
060public class TermConceptProperty implements Serializable {
061        public static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
062        private static final long serialVersionUID = 1L;
063        private static final int MAX_LENGTH = 500;
064
065        @ManyToOne(fetch = FetchType.LAZY)
066        @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
067        private TermConcept myConcept;
068
069        /**
070         * TODO: Make this non-null
071         *
072         * @since 3.5.0
073         */
074        @ManyToOne(fetch = FetchType.LAZY)
075        @JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CSV"))
076        private TermCodeSystemVersion myCodeSystemVersion;
077
078        @Id()
079        @SequenceGenerator(name = "SEQ_CONCEPT_PROP_PID", sequenceName = "SEQ_CONCEPT_PROP_PID")
080        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID")
081        @Column(name = "PID")
082        private Long myId;
083
084        @Column(name = "PROP_KEY", nullable = false, length = MAX_LENGTH)
085        @NotBlank
086        @GenericField(searchable = Searchable.YES)
087        private String myKey;
088
089        @Column(name = "PROP_VAL", nullable = true, length = MAX_LENGTH)
090        @FullTextField(searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
091        @GenericField(name = "myValueString", searchable = Searchable.YES)
092        private String myValue;
093
094        @Column(name = "PROP_VAL_LOB")
095        @Lob()
096        private byte[] myValueLob;
097
098        @Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH)
099        private TermConceptPropertyTypeEnum myType;
100
101        /**
102         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
103         */
104        @Column(name = "PROP_CODESYSTEM", length = MAX_LENGTH, nullable = true)
105        private String myCodeSystem;
106
107        /**
108         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
109         */
110        @Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true)
111        @GenericField(name = "myDisplayString", searchable = Searchable.YES)
112        private String myDisplay;
113
114        /**
115         * Constructor
116         */
117        public TermConceptProperty() {
118                super();
119        }
120
121        /**
122         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
123         */
124        public String getCodeSystem() {
125                return myCodeSystem;
126        }
127
128        /**
129         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
130         */
131        public TermConceptProperty setCodeSystem(String theCodeSystem) {
132                ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystem, MAX_LENGTH,
133                        "Property code system exceeds maximum length (" + MAX_LENGTH + "): " + length(theCodeSystem));
134                myCodeSystem = theCodeSystem;
135                return this;
136        }
137
138        /**
139         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
140         */
141        public String getDisplay() {
142                return myDisplay;
143        }
144
145        /**
146         * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
147         */
148        public TermConceptProperty setDisplay(String theDisplay) {
149                myDisplay = left(theDisplay, MAX_LENGTH);
150                return this;
151        }
152
153        public String getKey() {
154                return myKey;
155        }
156
157        public TermConceptProperty setKey(@Nonnull String theKey) {
158                ValidateUtil.isNotBlankOrThrowIllegalArgument(theKey, "theKey must not be null or empty");
159                ValidateUtil.isNotTooLongOrThrowIllegalArgument(theKey, MAX_LENGTH,
160                        "Code exceeds maximum length (" + MAX_LENGTH + "): " + length(theKey));
161                myKey = theKey;
162                return this;
163        }
164
165        public TermConceptPropertyTypeEnum getType() {
166                return myType;
167        }
168
169        public TermConceptProperty setType(@Nonnull TermConceptPropertyTypeEnum theType) {
170                Validate.notNull(theType);
171                myType = theType;
172                return this;
173        }
174
175        /**
176         * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string}
177         * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property.
178         */
179        public String getValue() {
180                if (hasValueLob()) {
181                        return getValueLobAsString();
182                }
183                return myValue;
184        }
185
186        /**
187         * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string}
188         * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property.
189         */
190        public TermConceptProperty setValue(String theValue) {
191                if (theValue.length() > MAX_LENGTH) {
192                        setValueLob(theValue);
193                } else {
194                        myValueLob = null;
195                }
196                myValue = left(theValue, MAX_LENGTH);
197                return this;
198        }
199
200        public boolean hasValueLob() {
201                if (myValueLob != null && myValueLob.length > 0) {
202                        return true;
203                }
204                return false;
205        }
206
207        public byte[] getValueLob() {
208                return myValueLob;
209        }
210
211        public TermConceptProperty setValueLob(byte[] theValueLob) {
212                myValueLob = theValueLob;
213                return this;
214        }
215
216        public TermConceptProperty setValueLob(String theValueLob) {
217                myValueLob = theValueLob.getBytes(StandardCharsets.UTF_8);
218                return this;
219        }
220
221        public String getValueLobAsString() {
222                return new String(myValueLob, StandardCharsets.UTF_8);
223        }
224
225        public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
226                myCodeSystemVersion = theCodeSystemVersion;
227                return this;
228        }
229
230        public TermConceptProperty setConcept(TermConcept theConcept) {
231                myConcept = theConcept;
232                return this;
233        }
234
235        @Override
236        public String toString() {
237                return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
238                        .append("conceptPid", myConcept.getId())
239                        .append("key", myKey)
240                        .append("value", getValue())
241                        .toString();
242        }
243
244        @Override
245        public boolean equals(Object theO) {
246                if (this == theO) {
247                        return true;
248                }
249
250                if (theO == null || getClass() != theO.getClass()) {
251                        return false;
252                }
253
254                TermConceptProperty that = (TermConceptProperty) theO;
255
256                return new EqualsBuilder()
257                        .append(myKey, that.myKey)
258                        .append(myValue, that.myValue)
259                        .append(myType, that.myType)
260                        .append(myCodeSystem, that.myCodeSystem)
261                        .append(myDisplay, that.myDisplay)
262                        .isEquals();
263        }
264
265        @Override
266        public int hashCode() {
267                return new HashCodeBuilder(17, 37)
268                        .append(myKey)
269                        .append(myValue)
270                        .append(myType)
271                        .append(myCodeSystem)
272                        .append(myDisplay)
273                        .toHashCode();
274        }
275
276        public Long getPid() {
277                return myId;
278        }
279}