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}