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.model.api.IQueryParameterType; 025import ca.uhn.fhir.rest.param.QuantityParam; 026import jakarta.persistence.Column; 027import jakarta.persistence.Entity; 028import jakarta.persistence.EntityListeners; 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.Index; 036import jakarta.persistence.JoinColumn; 037import jakarta.persistence.JoinColumns; 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.hibernate.annotations.GenericGenerator; 045import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField; 046 047import java.math.BigDecimal; 048import java.util.Objects; 049 050import static org.apache.commons.lang3.StringUtils.defaultString; 051import static org.apache.commons.lang3.StringUtils.isBlank; 052 053// @formatter:off 054@EntityListeners(IndexStorageOptimizationListener.class) 055@Entity 056@Table( 057 name = "HFJ_SPIDX_QUANTITY", 058 indexes = { 059 // We used to have an index named IDX_SP_QUANTITY - Dont reuse 060 @Index(name = "IDX_SP_QUANTITY_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"), 061 @Index( 062 name = "IDX_SP_QUANTITY_HASH_UN_V2", 063 columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"), 064 @Index( 065 name = "IDX_SP_QUANTITY_HASH_SYSUN_V2", 066 columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"), 067 @Index( 068 name = "IDX_SP_QUANTITY_RESID_V2", 069 columnList = 070 "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID") 071 }) 072@IdClass(IdAndPartitionId.class) 073public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearchParamQuantity { 074 075 private static final long serialVersionUID = 1L; 076 077 @Id 078 @GenericGenerator( 079 name = "SEQ_SPIDX_QUANTITY", 080 type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 081 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY") 082 @Column(name = "SP_ID") 083 private Long myId; 084 085 @Column(name = "SP_VALUE", nullable = true) 086 @ScaledNumberField 087 public Double myValue; 088 089 @ManyToOne( 090 optional = false, 091 fetch = FetchType.LAZY, 092 cascade = {}) 093 @JoinColumns( 094 value = { 095 @JoinColumn( 096 name = "RES_ID", 097 referencedColumnName = "RES_ID", 098 insertable = false, 099 updatable = false, 100 nullable = false), 101 @JoinColumn( 102 name = "PARTITION_ID", 103 referencedColumnName = "PARTITION_ID", 104 insertable = false, 105 updatable = false, 106 nullable = false) 107 }, 108 foreignKey = @ForeignKey(name = "FK_SP_QUANTITY_RES")) 109 private ResourceTable myResource; 110 111 @Column(name = "RES_ID", nullable = false) 112 private Long myResourceId; 113 114 public ResourceIndexedSearchParamQuantity() { 115 super(); 116 } 117 118 public ResourceIndexedSearchParamQuantity( 119 PartitionSettings thePartitionSettings, 120 String theResourceType, 121 String theParamName, 122 BigDecimal theValue, 123 String theSystem, 124 String theUnits) { 125 this(); 126 setPartitionSettings(thePartitionSettings); 127 setResourceType(theResourceType); 128 setParamName(theParamName); 129 setSystem(theSystem); 130 setValue(theValue); 131 setUnits(theUnits); 132 calculateHashes(); 133 } 134 135 @Override 136 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 137 super.copyMutableValuesFrom(theSource); 138 ResourceIndexedSearchParamQuantity source = (ResourceIndexedSearchParamQuantity) theSource; 139 mySystem = source.mySystem; 140 myUnits = source.myUnits; 141 myValue = source.myValue; 142 setHashIdentity(source.getHashIdentity()); 143 setHashIdentityAndUnits(source.getHashIdentityAndUnits()); 144 setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits()); 145 } 146 147 @Override 148 public void setResourceId(Long theResourceId) { 149 myResourceId = theResourceId; 150 } 151 152 public BigDecimal getValue() { 153 return myValue != null ? new BigDecimal(myValue) : null; 154 } 155 156 public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) { 157 myValue = theValue != null ? theValue.doubleValue() : null; 158 return this; 159 } 160 161 @Override 162 public Long getId() { 163 return myId; 164 } 165 166 @Override 167 public void setId(Long theId) { 168 myId = theId; 169 } 170 171 @Override 172 public IQueryParameterType toQueryParameterType() { 173 return new QuantityParam(null, getValue(), getSystem(), getUnits()); 174 } 175 176 @Override 177 public String toString() { 178 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 179 b.append("paramName", getParamName()); 180 b.append("resourceId", getResourcePid()); 181 b.append("system", getSystem()); 182 b.append("units", getUnits()); 183 b.append("value", getValue()); 184 b.append("missing", isMissing()); 185 b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits()); 186 return b.build(); 187 } 188 189 @Override 190 public boolean equals(Object theObj) { 191 if (this == theObj) { 192 return true; 193 } 194 if (theObj == null) { 195 return false; 196 } 197 if (!(theObj instanceof ResourceIndexedSearchParamQuantity)) { 198 return false; 199 } 200 ResourceIndexedSearchParamQuantity obj = (ResourceIndexedSearchParamQuantity) theObj; 201 EqualsBuilder b = new EqualsBuilder(); 202 b.append(getHashIdentity(), obj.getHashIdentity()); 203 b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits()); 204 b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits()); 205 b.append(isMissing(), obj.isMissing()); 206 b.append(getValue(), obj.getValue()); 207 return b.isEquals(); 208 } 209 210 @Override 211 public int hashCode() { 212 HashCodeBuilder b = new HashCodeBuilder(); 213 b.append(getHashIdentity()); 214 b.append(getHashIdentityAndUnits()); 215 b.append(getHashIdentitySystemAndUnits()); 216 b.append(isMissing()); 217 b.append(getValue()); 218 return b.toHashCode(); 219 } 220 221 @Override 222 public boolean matches(IQueryParameterType theParam) { 223 224 if (!(theParam instanceof QuantityParam)) { 225 return false; 226 } 227 QuantityParam quantity = (QuantityParam) theParam; 228 boolean retval = false; 229 230 // Only match on system if it wasn't specified 231 String quantityUnitsString = defaultString(quantity.getUnits()); 232 if (quantity.getSystem() == null && isBlank(quantityUnitsString)) { 233 if (Objects.equals(getValue(), quantity.getValue())) { 234 retval = true; 235 } 236 } else { 237 String unitsString = defaultString(getUnits()); 238 if (quantity.getSystem() == null) { 239 if (unitsString.equalsIgnoreCase(quantityUnitsString) 240 && Objects.equals(getValue(), quantity.getValue())) { 241 retval = true; 242 } 243 } else if (isBlank(quantityUnitsString)) { 244 if (getSystem().equalsIgnoreCase(quantity.getSystem()) 245 && Objects.equals(getValue(), quantity.getValue())) { 246 retval = true; 247 } 248 } else { 249 if (getSystem().equalsIgnoreCase(quantity.getSystem()) 250 && unitsString.equalsIgnoreCase(quantityUnitsString) 251 && Objects.equals(getValue(), quantity.getValue())) { 252 retval = true; 253 } 254 } 255 } 256 257 return retval; 258 } 259 260 @Override 261 public ResourceTable getResource() { 262 return myResource; 263 } 264 265 @Override 266 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 267 myResource = theResource; 268 setResourceType(theResource.getResourceType()); 269 return this; 270 } 271}