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