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.NumberParam; 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.annotations.JdbcTypeCode; 046import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField; 047import org.hibernate.type.SqlTypes; 048 049import java.math.BigDecimal; 050import java.util.Objects; 051 052@EntityListeners(IndexStorageOptimizationListener.class) 053@Entity 054@Table( 055 name = "HFJ_SPIDX_NUMBER", 056 indexes = { 057 // We used to have an index with name IDX_SP_NUMBER - Dont reuse 058 @Index(name = "IDX_SP_NUMBER_HASH_VAL_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"), 059 @Index(name = "IDX_SP_NUMBER_RESID_V2", columnList = "RES_ID, HASH_IDENTITY, SP_VALUE, PARTITION_ID") 060 }) 061@IdClass(IdAndPartitionId.class) 062public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam { 063 064 private static final long serialVersionUID = 1L; 065 066 @Column(name = "SP_VALUE", nullable = true, precision = 19, scale = 2) 067 @ScaledNumberField 068 @JdbcTypeCode(SqlTypes.DECIMAL) 069 public BigDecimal myValue; 070 071 @Id 072 @GenericGenerator(name = "SEQ_SPIDX_NUMBER", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class) 073 @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_NUMBER") 074 @Column(name = "SP_ID") 075 private Long myId; 076 077 @ManyToOne( 078 optional = false, 079 fetch = FetchType.LAZY, 080 cascade = {}) 081 @JoinColumns( 082 value = { 083 @JoinColumn( 084 name = "RES_ID", 085 referencedColumnName = "RES_ID", 086 insertable = false, 087 updatable = false, 088 nullable = false), 089 @JoinColumn( 090 name = "PARTITION_ID", 091 referencedColumnName = "PARTITION_ID", 092 insertable = false, 093 updatable = false, 094 nullable = false) 095 }, 096 foreignKey = @ForeignKey(name = "FK_SP_NUMBER_RES")) 097 private ResourceTable myResource; 098 099 @Column(name = "RES_ID", nullable = false) 100 private Long myResourceId; 101 102 public ResourceIndexedSearchParamNumber() {} 103 104 public ResourceIndexedSearchParamNumber( 105 PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue) { 106 setPartitionSettings(thePartitionSettings); 107 setResourceType(theResourceType); 108 setParamName(theParamName); 109 setValue(theValue); 110 calculateHashes(); 111 } 112 113 @Override 114 public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) { 115 super.copyMutableValuesFrom(theSource); 116 ResourceIndexedSearchParamNumber source = (ResourceIndexedSearchParamNumber) theSource; 117 myValue = source.myValue; 118 myHashIdentity = source.myHashIdentity; 119 } 120 121 @Override 122 public void setResourceId(Long theResourceId) { 123 myResourceId = theResourceId; 124 } 125 126 @Override 127 public void clearHashes() { 128 myHashIdentity = null; 129 } 130 131 @Override 132 public void calculateHashes() { 133 if (myHashIdentity != null) { 134 return; 135 } 136 String resourceType = getResourceType(); 137 String paramName = getParamName(); 138 setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); 139 } 140 141 @Override 142 public boolean equals(Object theObj) { 143 if (this == theObj) { 144 return true; 145 } 146 if (theObj == null) { 147 return false; 148 } 149 if (!(theObj instanceof ResourceIndexedSearchParamNumber)) { 150 return false; 151 } 152 ResourceIndexedSearchParamNumber obj = (ResourceIndexedSearchParamNumber) theObj; 153 EqualsBuilder b = new EqualsBuilder(); 154 b.append(getHashIdentity(), obj.getHashIdentity()); 155 b.append(normalizeForEqualityComparison(getValue()), normalizeForEqualityComparison(obj.getValue())); 156 b.append(isMissing(), obj.isMissing()); 157 return b.isEquals(); 158 } 159 160 private Double normalizeForEqualityComparison(BigDecimal theValue) { 161 if (theValue == null) { 162 return null; 163 } 164 return theValue.doubleValue(); 165 } 166 167 @Override 168 public Long getId() { 169 return myId; 170 } 171 172 @Override 173 public void setId(Long theId) { 174 myId = theId; 175 } 176 177 public BigDecimal getValue() { 178 return myValue; 179 } 180 181 public void setValue(BigDecimal theValue) { 182 myValue = theValue; 183 } 184 185 @Override 186 public int hashCode() { 187 HashCodeBuilder b = new HashCodeBuilder(); 188 b.append(getHashIdentity()); 189 b.append(normalizeForEqualityComparison(getValue())); 190 b.append(isMissing()); 191 return b.toHashCode(); 192 } 193 194 @Override 195 public IQueryParameterType toQueryParameterType() { 196 return new NumberParam(myValue.toPlainString()); 197 } 198 199 @Override 200 public String toString() { 201 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 202 b.append("paramName", getParamName()); 203 b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this 204 b.append("value", getValue()); 205 return b.build(); 206 } 207 208 @Override 209 public boolean matches(IQueryParameterType theParam) { 210 if (!(theParam instanceof NumberParam)) { 211 return false; 212 } 213 NumberParam number = (NumberParam) theParam; 214 return Objects.equals(getValue(), number.getValue()); 215 } 216 217 @Override 218 public ResourceTable getResource() { 219 return myResource; 220 } 221 222 @Override 223 public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) { 224 myResource = theResource; 225 setResourceType(theResource.getResourceType()); 226 return this; 227 } 228}