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