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}