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