001/* 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 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.jpa.model.entity.BasePartitionable; 023import ca.uhn.fhir.jpa.model.entity.IdAndPartitionId; 024import ca.uhn.fhir.util.ValidateUtil; 025import com.google.common.annotations.VisibleForTesting; 026import jakarta.annotation.Nonnull; 027import jakarta.persistence.Column; 028import jakarta.persistence.Entity; 029import jakarta.persistence.FetchType; 030import jakarta.persistence.ForeignKey; 031import jakarta.persistence.GeneratedValue; 032import jakarta.persistence.GenerationType; 033import jakarta.persistence.Id; 034import jakarta.persistence.IdClass; 035import jakarta.persistence.JoinColumn; 036import jakarta.persistence.JoinColumns; 037import jakarta.persistence.Lob; 038import jakarta.persistence.ManyToOne; 039import jakarta.persistence.OneToMany; 040import jakarta.persistence.SequenceGenerator; 041import jakarta.persistence.Table; 042import jakarta.persistence.Transient; 043import jakarta.persistence.UniqueConstraint; 044import org.apache.commons.lang3.builder.EqualsBuilder; 045import org.apache.commons.lang3.builder.HashCodeBuilder; 046import org.apache.commons.lang3.builder.ToStringBuilder; 047import org.apache.commons.lang3.builder.ToStringStyle; 048import org.hibernate.Length; 049 050import java.io.Serializable; 051import java.util.ArrayList; 052import java.util.List; 053 054import static java.util.Objects.nonNull; 055import static org.apache.commons.lang3.StringUtils.isNotEmpty; 056import static org.apache.commons.lang3.StringUtils.left; 057import static org.apache.commons.lang3.StringUtils.length; 058 059/* 060 * DM 2019-08-01 - Do not use IDX_VALUESET_CONCEPT_CS_CD or IDX_VALUESET_CONCEPT_CS_CODE; this was previously used as an index so reusing the name will 061 * bork up migration tasks. 062 */ 063@Table( 064 name = "TRM_VALUESET_CONCEPT", 065 uniqueConstraints = { 066 @UniqueConstraint( 067 name = "IDX_VS_CONCEPT_CSCD", 068 columnNames = {"PARTITION_ID", "VALUESET_PID", "SYSTEM_URL", "CODEVAL"}), 069 @UniqueConstraint( 070 name = "IDX_VS_CONCEPT_ORDER", 071 columnNames = {"PARTITION_ID", "VALUESET_PID", "VALUESET_ORDER"}) 072 }) 073@Entity() 074@IdClass(IdAndPartitionId.class) 075public class TermValueSetConcept extends BasePartitionable implements Serializable { 076 private static final long serialVersionUID = 1L; 077 078 @Id() 079 @SequenceGenerator(name = "SEQ_VALUESET_CONCEPT_PID", sequenceName = "SEQ_VALUESET_CONCEPT_PID") 080 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_CONCEPT_PID") 081 @Column(name = "PID") 082 private Long myId; 083 084 @ManyToOne(fetch = FetchType.LAZY) 085 @JoinColumns( 086 value = { 087 @JoinColumn( 088 name = "VALUESET_PID", 089 referencedColumnName = "PID", 090 insertable = true, 091 updatable = false, 092 nullable = false), 093 @JoinColumn( 094 name = "PARTITION_ID", 095 referencedColumnName = "PARTITION_ID", 096 insertable = true, 097 updatable = false, 098 nullable = false) 099 }, 100 foreignKey = @ForeignKey(name = "FK_TRM_VALUESET_PID")) 101 private TermValueSet myValueSet; 102 103 @Column(name = "VALUESET_PID", insertable = false, updatable = false, nullable = false) 104 private Long myValueSetPid; 105 106 @Column(name = "INDEX_STATUS", nullable = true) 107 private Long myIndexStatus; 108 109 @Column(name = "VALUESET_ORDER", nullable = false) 110 private int myOrder; 111 112 @Transient 113 private String myValueSetUrl; 114 115 @Transient 116 private String myValueSetName; 117 118 @Column(name = "SOURCE_PID", nullable = true) 119 private Long mySourceConceptPid; 120 121 @Deprecated(since = "7.2.0") 122 @Lob 123 @Column(name = "SOURCE_DIRECT_PARENT_PIDS", nullable = true) 124 private String mySourceConceptDirectParentPids; 125 126 @Column(name = "SOURCE_DIRECT_PARENT_PIDS_VC", nullable = true, length = Length.LONG32) 127 private String mySourceConceptDirectParentPidsVc; 128 129 @Column(name = "SYSTEM_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) 130 private String mySystem; 131 132 @Column(name = "SYSTEM_VER", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) 133 private String mySystemVer; 134 135 @Column(name = "CODEVAL", nullable = false, length = TermConcept.MAX_CODE_LENGTH) 136 private String myCode; 137 138 @Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) 139 private String myDisplay; 140 141 @OneToMany(mappedBy = "myConcept", fetch = FetchType.LAZY) 142 private List<TermValueSetConceptDesignation> myDesignations; 143 144 @Transient 145 private transient Integer myHashCode; 146 147 /** 148 * Constructor 149 */ 150 public TermValueSetConcept() { 151 super(); 152 } 153 154 public Long getId() { 155 return myId; 156 } 157 158 public IdAndPartitionId getPartitionedId() { 159 return IdAndPartitionId.forId(myId, this); 160 } 161 162 public TermValueSet getValueSet() { 163 return myValueSet; 164 } 165 166 public TermValueSetConcept setValueSet(TermValueSet theValueSet) { 167 myValueSet = theValueSet; 168 setPartitionId(theValueSet.getPartitionId()); 169 return this; 170 } 171 172 public int getOrder() { 173 return myOrder; 174 } 175 176 public TermValueSetConcept setOrder(int theOrder) { 177 myOrder = theOrder; 178 return this; 179 } 180 181 public String getValueSetUrl() { 182 if (myValueSetUrl == null) { 183 myValueSetUrl = getValueSet().getUrl(); 184 } 185 186 return myValueSetUrl; 187 } 188 189 public String getValueSetName() { 190 if (myValueSetName == null) { 191 myValueSetName = getValueSet().getName(); 192 } 193 194 return myValueSetName; 195 } 196 197 public String getSystem() { 198 return mySystem; 199 } 200 201 public TermValueSetConcept setSystem(@Nonnull String theSystem) { 202 ValidateUtil.isNotBlankOrThrowIllegalArgument(theSystem, "theSystem must not be null or empty"); 203 ValidateUtil.isNotTooLongOrThrowIllegalArgument( 204 theSystem, 205 TermCodeSystem.MAX_URL_LENGTH, 206 "System exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSystem)); 207 mySystem = theSystem; 208 return this; 209 } 210 211 public String getSystemVersion() { 212 return mySystemVer; 213 } 214 215 public TermValueSetConcept setSystemVersion(String theSystemVersion) { 216 ValidateUtil.isNotTooLongOrThrowIllegalArgument( 217 theSystemVersion, 218 TermCodeSystemVersion.MAX_VERSION_LENGTH, 219 "System version exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): " 220 + length(theSystemVersion)); 221 mySystemVer = theSystemVersion; 222 return this; 223 } 224 225 public String getCode() { 226 return myCode; 227 } 228 229 public TermValueSetConcept setCode(@Nonnull String theCode) { 230 ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); 231 ValidateUtil.isNotTooLongOrThrowIllegalArgument( 232 theCode, 233 TermConcept.MAX_CODE_LENGTH, 234 "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); 235 myCode = theCode; 236 return this; 237 } 238 239 public String getDisplay() { 240 return myDisplay; 241 } 242 243 public TermValueSetConcept setDisplay(String theDisplay) { 244 myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); 245 return this; 246 } 247 248 public List<TermValueSetConceptDesignation> getDesignations() { 249 if (myDesignations == null) { 250 myDesignations = new ArrayList<>(); 251 } 252 253 return myDesignations; 254 } 255 256 @Override 257 public boolean equals(Object theO) { 258 if (this == theO) return true; 259 260 if (!(theO instanceof TermValueSetConcept)) return false; 261 262 TermValueSetConcept that = (TermValueSetConcept) theO; 263 264 return new EqualsBuilder() 265 .append(myValueSetPid, that.myValueSetPid) 266 .append(getSystem(), that.getSystem()) 267 .append(getCode(), that.getCode()) 268 .isEquals(); 269 } 270 271 @Override 272 public int hashCode() { 273 if (myHashCode == null) { 274 myHashCode = new HashCodeBuilder(17, 37) 275 .append(myValueSetPid) 276 .append(getSystem()) 277 .append(getCode()) 278 .toHashCode(); 279 } 280 return myHashCode; 281 } 282 283 @Override 284 public String toString() { 285 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 286 .append("id", myId) 287 .append("order", myOrder) 288 .append("system", mySystem) 289 .append("code", myCode) 290 .append("valueSet", myValueSet != null ? myValueSet.getId() : "(null)") 291 .append("valueSetPid", myValueSetPid) 292 .append("valueSetUrl", this.getValueSetUrl()) 293 .append("valueSetName", this.getValueSetName()) 294 .append("display", myDisplay) 295 .append("designationCount", myDesignations != null ? myDesignations.size() : "(null)") 296 .append("parentPids", getSourceConceptDirectParentPids()) 297 .toString(); 298 } 299 300 public Long getIndexStatus() { 301 return myIndexStatus; 302 } 303 304 public void setIndexStatus(Long theIndexStatus) { 305 myIndexStatus = theIndexStatus; 306 } 307 308 public void setSourceConceptPid(Long theSourceConceptPid) { 309 mySourceConceptPid = theSourceConceptPid; 310 } 311 312 public void setSourceConceptDirectParentPids(String theSourceConceptDirectParentPids) { 313 mySourceConceptDirectParentPids = theSourceConceptDirectParentPids; 314 mySourceConceptDirectParentPidsVc = theSourceConceptDirectParentPids; 315 } 316 317 public String getSourceConceptDirectParentPids() { 318 return isNotEmpty(mySourceConceptDirectParentPidsVc) 319 ? mySourceConceptDirectParentPidsVc 320 : mySourceConceptDirectParentPids; 321 } 322 323 public void clearSourceConceptDirectParentPidsLob() { 324 mySourceConceptDirectParentPids = null; 325 } 326 327 @VisibleForTesting 328 public boolean hasSourceConceptDirectParentPidsLob() { 329 return nonNull(mySourceConceptDirectParentPids); 330 } 331}