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}