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.QuantityParam;
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.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
045
046import java.math.BigDecimal;
047import java.util.Objects;
048
049import static org.apache.commons.lang3.StringUtils.defaultString;
050import static org.apache.commons.lang3.StringUtils.isBlank;
051
052// @formatter:off
053@Embeddable
054@EntityListeners(IndexStorageOptimizationListener.class)
055@Entity
056@Table(
057                name = "HFJ_SPIDX_QUANTITY",
058                indexes = {
059                        //      We used to have an index named IDX_SP_QUANTITY - Dont reuse
060                        @Index(name = "IDX_SP_QUANTITY_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"),
061                        @Index(
062                                        name = "IDX_SP_QUANTITY_HASH_UN_V2",
063                                        columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
064                        @Index(
065                                        name = "IDX_SP_QUANTITY_HASH_SYSUN_V2",
066                                        columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
067                        @Index(
068                                        name = "IDX_SP_QUANTITY_RESID_V2",
069                                        columnList =
070                                                        "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID")
071                })
072public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearchParamQuantity {
073
074        private static final long serialVersionUID = 1L;
075
076        @Id
077        @GenericGenerator(
078                        name = "SEQ_SPIDX_QUANTITY",
079                        type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
080        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
081        @Column(name = "SP_ID")
082        private Long myId;
083
084        @Column(name = "SP_VALUE", nullable = true)
085        @ScaledNumberField
086        public Double myValue;
087
088        @ManyToOne(
089                        optional = false,
090                        fetch = FetchType.LAZY,
091                        cascade = {})
092        @JoinColumn(
093                        foreignKey = @ForeignKey(name = "FK_SP_QUANTITY_RES"),
094                        name = "RES_ID",
095                        referencedColumnName = "RES_ID",
096                        nullable = false)
097        private ResourceTable myResource;
098
099        public ResourceIndexedSearchParamQuantity() {
100                super();
101        }
102
103        public ResourceIndexedSearchParamQuantity(
104                        PartitionSettings thePartitionSettings,
105                        String theResourceType,
106                        String theParamName,
107                        BigDecimal theValue,
108                        String theSystem,
109                        String theUnits) {
110                this();
111                setPartitionSettings(thePartitionSettings);
112                setResourceType(theResourceType);
113                setParamName(theParamName);
114                setSystem(theSystem);
115                setValue(theValue);
116                setUnits(theUnits);
117                calculateHashes();
118        }
119
120        @Override
121        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
122                super.copyMutableValuesFrom(theSource);
123                ResourceIndexedSearchParamQuantity source = (ResourceIndexedSearchParamQuantity) theSource;
124                mySystem = source.mySystem;
125                myUnits = source.myUnits;
126                myValue = source.myValue;
127                setHashIdentity(source.getHashIdentity());
128                setHashIdentityAndUnits(source.getHashIdentityAndUnits());
129                setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits());
130        }
131
132        public BigDecimal getValue() {
133                return myValue != null ? new BigDecimal(myValue) : null;
134        }
135
136        public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) {
137                myValue = theValue != null ? theValue.doubleValue() : null;
138                return this;
139        }
140
141        @Override
142        public Long getId() {
143                return myId;
144        }
145
146        @Override
147        public void setId(Long theId) {
148                myId = theId;
149        }
150
151        @Override
152        public IQueryParameterType toQueryParameterType() {
153                return new QuantityParam(null, getValue(), getSystem(), getUnits());
154        }
155
156        @Override
157        public String toString() {
158                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
159                b.append("paramName", getParamName());
160                b.append("resourceId", getResourcePid());
161                b.append("system", getSystem());
162                b.append("units", getUnits());
163                b.append("value", getValue());
164                b.append("missing", isMissing());
165                b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits());
166                return b.build();
167        }
168
169        @Override
170        public boolean equals(Object theObj) {
171                if (this == theObj) {
172                        return true;
173                }
174                if (theObj == null) {
175                        return false;
176                }
177                if (!(theObj instanceof ResourceIndexedSearchParamQuantity)) {
178                        return false;
179                }
180                ResourceIndexedSearchParamQuantity obj = (ResourceIndexedSearchParamQuantity) theObj;
181                EqualsBuilder b = new EqualsBuilder();
182                b.append(getHashIdentity(), obj.getHashIdentity());
183                b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
184                b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
185                b.append(isMissing(), obj.isMissing());
186                b.append(getValue(), obj.getValue());
187                return b.isEquals();
188        }
189
190        @Override
191        public int hashCode() {
192                HashCodeBuilder b = new HashCodeBuilder();
193                b.append(getHashIdentity());
194                b.append(getHashIdentityAndUnits());
195                b.append(getHashIdentitySystemAndUnits());
196                b.append(isMissing());
197                b.append(getValue());
198                return b.toHashCode();
199        }
200
201        @Override
202        public boolean matches(IQueryParameterType theParam) {
203
204                if (!(theParam instanceof QuantityParam)) {
205                        return false;
206                }
207                QuantityParam quantity = (QuantityParam) theParam;
208                boolean retval = false;
209
210                // Only match on system if it wasn't specified
211                String quantityUnitsString = defaultString(quantity.getUnits());
212                if (quantity.getSystem() == null && isBlank(quantityUnitsString)) {
213                        if (Objects.equals(getValue(), quantity.getValue())) {
214                                retval = true;
215                        }
216                } else {
217                        String unitsString = defaultString(getUnits());
218                        if (quantity.getSystem() == null) {
219                                if (unitsString.equalsIgnoreCase(quantityUnitsString)
220                                                && Objects.equals(getValue(), quantity.getValue())) {
221                                        retval = true;
222                                }
223                        } else if (isBlank(quantityUnitsString)) {
224                                if (getSystem().equalsIgnoreCase(quantity.getSystem())
225                                                && Objects.equals(getValue(), quantity.getValue())) {
226                                        retval = true;
227                                }
228                        } else {
229                                if (getSystem().equalsIgnoreCase(quantity.getSystem())
230                                                && unitsString.equalsIgnoreCase(quantityUnitsString)
231                                                && Objects.equals(getValue(), quantity.getValue())) {
232                                        retval = true;
233                                }
234                        }
235                }
236
237                return retval;
238        }
239
240        @Override
241        public ResourceTable getResource() {
242                return myResource;
243        }
244
245        @Override
246        public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
247                myResource = theResource;
248                setResourceType(theResource.getResourceType());
249                return this;
250        }
251}