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}