001/* 002 * #%L 003 * HAPI FHIR JPA Model 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.model.entity; 021 022import ca.uhn.fhir.jpa.model.config.PartitionSettings; 023import ca.uhn.fhir.jpa.model.listener.IndexStorageOptimizationListener; 024import ca.uhn.fhir.jpa.model.util.UcumServiceUtil; 025import ca.uhn.fhir.model.api.IQueryParameterType; 026import ca.uhn.fhir.rest.param.QuantityParam; 027import jakarta.persistence.Column; 028import jakarta.persistence.Entity; 029import jakarta.persistence.EntityListeners; 030import jakarta.persistence.FetchType; 031import jakarta.persistence.ForeignKey; 032import jakarta.persistence.GeneratedValue; 033import jakarta.persistence.GenerationType; 034import jakarta.persistence.Id; 035import jakarta.persistence.IdClass; 036import jakarta.persistence.Index; 037import jakarta.persistence.JoinColumn; 038import jakarta.persistence.JoinColumns; 039import jakarta.persistence.ManyToOne; 040import jakarta.persistence.Table; 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.fhir.ucum.Pair; 046import org.hibernate.annotations.GenericGenerator; 047import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField; 048 049import java.math.BigDecimal; 050import java.util.Objects; 051 052import static org.apache.commons.lang3.StringUtils.defaultString; 053import static org.apache.commons.lang3.StringUtils.isBlank; 054 055// @formatter:off 056@EntityListeners(IndexStorageOptimizationListener.class) 057@Entity 058@Table( 059 name = "HFJ_SPIDX_QUANTITY_NRML", 060 indexes = { 061 @Index(name = "IDX_SP_QNTY_NRML_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"), 062 @Index( 063 name = "IDX_SP_QNTY_NRML_HASH_UN_V2", 064 columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"), 065 @Index( 066 name = "IDX_SP_QNTY_NRML_HASH_SYSUN_V2", 067 columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"), 068 @Index( 069 name = "IDX_SP_QNTY_NRML_RESID_V2", 070 columnList = 071 "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID") 072 }) 073/** 074 * Support UCUM service 075 * @since 5.3.0 076 * 077 */ 078@IdClass(IdAndPartitionId.class) 079public class ResourceIndexedSearchParamQuantityNormalized extends BaseResourceIndexedSearchParamQuantity { 080 081 private static final long serialVersionUID = 1L; 082 083 @Id 084 @GenericGenerator( 085 name = "SEQ_SPIDX_QUANTITY_NRML", 086 type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 087 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY_NRML") 088 @Column(name = "SP_ID") 089 private Long myId; 090 091 // Changed to double here for storing the value after converted to the CanonicalForm due to BigDecimal maps 092 // NUMBER(19,2) 093 // The precision may lost even to store 1.2cm which is 0.012m in the CanonicalForm 094 @Column(name = "SP_VALUE", nullable = true) 095 @ScaledNumberField 096 public Double myValue; 097 098 @ManyToOne( 099 optional = false, 100 fetch = FetchType.LAZY, 101 cascade = {}) 102 @JoinColumns( 103 value = { 104 @JoinColumn( 105 name = "RES_ID", 106 referencedColumnName = "RES_ID", 107 insertable = false, 108 updatable = false, 109 nullable = false), 110 @JoinColumn( 111 name = "PARTITION_ID", 112 referencedColumnName = "PARTITION_ID", 113 insertable = false, 114 updatable = false, 115 nullable = false) 116 }, 117 foreignKey = @ForeignKey(name = "FK_SP_QUANTITYNM_RES")) 118 private ResourceTable myResource; 119 120 @Column(name = "RES_ID", nullable = false) 121 private Long myResourceId; 122 123 public ResourceIndexedSearchParamQuantityNormalized() { 124 super(); 125 } 126 127 public ResourceIndexedSearchParamQuantityNormalized( 128 PartitionSettings thePartitionSettings, 129 String theResourceType, 130 String theParamName, 131 double theValue, 132 String theSystem, 133 String theUnits) { 134 this(); 135 setPartitionSettings(thePartitionSettings); 136 setResourceType(theResourceType); 137 setParamName(theParamName); 138 setSystem(theSystem); 139 setValue(theValue); 140 setUnits(theUnits); 141 calculateHashes(); 142 } 143 144 @Override 145 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 146 super.copyMutableValuesFrom(theSource); 147 ResourceIndexedSearchParamQuantityNormalized source = (ResourceIndexedSearchParamQuantityNormalized) theSource; 148 mySystem = source.mySystem; 149 myUnits = source.myUnits; 150 myValue = source.myValue; 151 setHashIdentity(source.getHashIdentity()); 152 setHashIdentityAndUnits(source.getHashIdentityAndUnits()); 153 setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits()); 154 } 155 156 @Override 157 public void setResourceId(Long theResourceId) { 158 myResourceId = theResourceId; 159 } 160 161 // - myValue 162 public Double getValue() { 163 return myValue; 164 } 165 166 public ResourceIndexedSearchParamQuantityNormalized setValue(Double theValue) { 167 myValue = theValue; 168 return this; 169 } 170 171 public ResourceIndexedSearchParamQuantityNormalized setValue(double theValue) { 172 myValue = theValue; 173 return this; 174 } 175 176 // -- myId 177 @Override 178 public Long getId() { 179 return myId; 180 } 181 182 @Override 183 public void setId(Long theId) { 184 myId = theId; 185 } 186 187 @Override 188 public IQueryParameterType toQueryParameterType() { 189 return new QuantityParam(null, getValue(), getSystem(), getUnits()); 190 } 191 192 @Override 193 public String toString() { 194 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 195 b.append("paramName", getParamName()); 196 b.append("resourceId", getResourcePid()); 197 b.append("system", getSystem()); 198 b.append("units", getUnits()); 199 b.append("value", getValue()); 200 b.append("missing", isMissing()); 201 b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits()); 202 return b.build(); 203 } 204 205 @Override 206 public boolean equals(Object theObj) { 207 if (this == theObj) { 208 return true; 209 } 210 if (theObj == null) { 211 return false; 212 } 213 if (!(theObj instanceof ResourceIndexedSearchParamQuantityNormalized)) { 214 return false; 215 } 216 ResourceIndexedSearchParamQuantityNormalized obj = (ResourceIndexedSearchParamQuantityNormalized) theObj; 217 EqualsBuilder b = new EqualsBuilder(); 218 b.append(getHashIdentity(), obj.getHashIdentity()); 219 b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits()); 220 b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits()); 221 b.append(isMissing(), obj.isMissing()); 222 b.append(getValue(), obj.getValue()); 223 return b.isEquals(); 224 } 225 226 @Override 227 public int hashCode() { 228 HashCodeBuilder b = new HashCodeBuilder(); 229 b.append(getHashIdentity()); 230 b.append(getHashIdentityAndUnits()); 231 b.append(getHashIdentitySystemAndUnits()); 232 b.append(isMissing()); 233 b.append(getValue()); 234 return b.toHashCode(); 235 } 236 237 @Override 238 public boolean matches(IQueryParameterType theParam) { 239 240 if (!(theParam instanceof QuantityParam)) { 241 return false; 242 } 243 QuantityParam quantity = (QuantityParam) theParam; 244 boolean retval = false; 245 246 String quantitySystem = quantity.getSystem(); 247 BigDecimal quantityValue = quantity.getValue(); 248 Double quantityDoubleValue = null; 249 if (quantityValue != null) quantityDoubleValue = quantityValue.doubleValue(); 250 String quantityUnits = defaultString(quantity.getUnits()); 251 252 // -- convert the value/unit to the canonical form if any, otherwise store the original value/units pair 253 Pair canonicalForm = UcumServiceUtil.getCanonicalForm(quantitySystem, quantityValue, quantityUnits); 254 if (canonicalForm != null) { 255 quantityDoubleValue = Double.parseDouble(canonicalForm.getValue().asDecimal()); 256 quantityUnits = canonicalForm.getCode(); 257 } 258 259 // Only match on system if it wasn't specified 260 if (quantitySystem == null && isBlank(quantityUnits)) { 261 if (Objects.equals(getValue(), quantityDoubleValue)) { 262 retval = true; 263 } 264 } else { 265 String unitsString = defaultString(getUnits()); 266 if (quantitySystem == null) { 267 if (unitsString.equalsIgnoreCase(quantityUnits) && Objects.equals(getValue(), quantityDoubleValue)) { 268 retval = true; 269 } 270 } else if (isBlank(quantityUnits)) { 271 if (getSystem().equalsIgnoreCase(quantitySystem) && Objects.equals(getValue(), quantityDoubleValue)) { 272 retval = true; 273 } 274 } else { 275 if (getSystem().equalsIgnoreCase(quantitySystem) 276 && unitsString.equalsIgnoreCase(quantityUnits) 277 && Objects.equals(getValue(), quantityDoubleValue)) { 278 retval = true; 279 } 280 } 281 } 282 283 return retval; 284 } 285 286 @Override 287 public ResourceTable getResource() { 288 return myResource; 289 } 290 291 @Override 292 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 293 myResource = theResource; 294 setResourceType(theResource.getResourceType()); 295 return this; 296 } 297}