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