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