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.jpa.model.util.UcumServiceUtil;
025import ca.uhn.fhir.model.api.IQueryParameterType;
026import ca.uhn.fhir.rest.param.QuantityParam;
027import jakarta.persistence.Column;
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.IdClass;
036import jakarta.persistence.Index;
037import jakarta.persistence.JoinColumn;
038import jakarta.persistence.JoinColumns;
039import jakarta.persistence.ManyToOne;
040import jakarta.persistence.Table;
041import org.apache.commons.lang3.builder.EqualsBuilder;
042import org.apache.commons.lang3.builder.HashCodeBuilder;
043import org.apache.commons.lang3.builder.ToStringBuilder;
044import org.apache.commons.lang3.builder.ToStringStyle;
045import org.fhir.ucum.Pair;
046import org.hibernate.annotations.GenericGenerator;
047import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
048
049import java.math.BigDecimal;
050import java.util.Objects;
051
052import static org.apache.commons.lang3.StringUtils.defaultString;
053import static org.apache.commons.lang3.StringUtils.isBlank;
054
055// @formatter:off
056@EntityListeners(IndexStorageOptimizationListener.class)
057@Entity
058@Table(
059                name = "HFJ_SPIDX_QUANTITY_NRML",
060                indexes = {
061                        @Index(name = "IDX_SP_QNTY_NRML_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"),
062                        @Index(
063                                        name = "IDX_SP_QNTY_NRML_HASH_UN_V2",
064                                        columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
065                        @Index(
066                                        name = "IDX_SP_QNTY_NRML_HASH_SYSUN_V2",
067                                        columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
068                        @Index(
069                                        name = "IDX_SP_QNTY_NRML_RESID_V2",
070                                        columnList =
071                                                        "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID")
072                })
073/**
074 * Support UCUM service
075 * @since 5.3.0
076 *
077 */
078@IdClass(IdAndPartitionId.class)
079public class ResourceIndexedSearchParamQuantityNormalized extends BaseResourceIndexedSearchParamQuantity {
080
081        private static final long serialVersionUID = 1L;
082
083        @Id
084        @GenericGenerator(
085                        name = "SEQ_SPIDX_QUANTITY_NRML",
086                        type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
087        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY_NRML")
088        @Column(name = "SP_ID")
089        private Long myId;
090
091        // Changed to double here for storing the value after converted to the CanonicalForm due to BigDecimal maps
092        // NUMBER(19,2)
093        // The precision may lost even to store 1.2cm which is 0.012m in the CanonicalForm
094        @Column(name = "SP_VALUE", nullable = true)
095        @ScaledNumberField
096        public Double myValue;
097
098        @ManyToOne(
099                        optional = false,
100                        fetch = FetchType.LAZY,
101                        cascade = {})
102        @JoinColumns(
103                        value = {
104                                @JoinColumn(
105                                                name = "RES_ID",
106                                                referencedColumnName = "RES_ID",
107                                                insertable = false,
108                                                updatable = false,
109                                                nullable = false),
110                                @JoinColumn(
111                                                name = "PARTITION_ID",
112                                                referencedColumnName = "PARTITION_ID",
113                                                insertable = false,
114                                                updatable = false,
115                                                nullable = false)
116                        },
117                        foreignKey = @ForeignKey(name = "FK_SP_QUANTITYNM_RES"))
118        private ResourceTable myResource;
119
120        @Column(name = "RES_ID", nullable = false)
121        private Long myResourceId;
122
123        public ResourceIndexedSearchParamQuantityNormalized() {
124                super();
125        }
126
127        public ResourceIndexedSearchParamQuantityNormalized(
128                        PartitionSettings thePartitionSettings,
129                        String theResourceType,
130                        String theParamName,
131                        double theValue,
132                        String theSystem,
133                        String theUnits) {
134                this();
135                setPartitionSettings(thePartitionSettings);
136                setResourceType(theResourceType);
137                setParamName(theParamName);
138                setSystem(theSystem);
139                setValue(theValue);
140                setUnits(theUnits);
141                calculateHashes();
142        }
143
144        @Override
145        public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
146                super.copyMutableValuesFrom(theSource);
147                ResourceIndexedSearchParamQuantityNormalized source = (ResourceIndexedSearchParamQuantityNormalized) theSource;
148                mySystem = source.mySystem;
149                myUnits = source.myUnits;
150                myValue = source.myValue;
151                setHashIdentity(source.getHashIdentity());
152                setHashIdentityAndUnits(source.getHashIdentityAndUnits());
153                setHashIdentitySystemAndUnits(source.getHashIdentitySystemAndUnits());
154        }
155
156        @Override
157        public void setResourceId(Long theResourceId) {
158                myResourceId = theResourceId;
159        }
160
161        // - myValue
162        public Double getValue() {
163                return myValue;
164        }
165
166        public ResourceIndexedSearchParamQuantityNormalized setValue(Double theValue) {
167                myValue = theValue;
168                return this;
169        }
170
171        public ResourceIndexedSearchParamQuantityNormalized setValue(double theValue) {
172                myValue = theValue;
173                return this;
174        }
175
176        // -- myId
177        @Override
178        public Long getId() {
179                return myId;
180        }
181
182        @Override
183        public void setId(Long theId) {
184                myId = theId;
185        }
186
187        @Override
188        public IQueryParameterType toQueryParameterType() {
189                return new QuantityParam(null, getValue(), getSystem(), getUnits());
190        }
191
192        @Override
193        public String toString() {
194                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
195                b.append("paramName", getParamName());
196                b.append("resourceId", getResourcePid());
197                b.append("system", getSystem());
198                b.append("units", getUnits());
199                b.append("value", getValue());
200                b.append("missing", isMissing());
201                b.append("hashIdentitySystemAndUnits", getHashIdentitySystemAndUnits());
202                return b.build();
203        }
204
205        @Override
206        public boolean equals(Object theObj) {
207                if (this == theObj) {
208                        return true;
209                }
210                if (theObj == null) {
211                        return false;
212                }
213                if (!(theObj instanceof ResourceIndexedSearchParamQuantityNormalized)) {
214                        return false;
215                }
216                ResourceIndexedSearchParamQuantityNormalized obj = (ResourceIndexedSearchParamQuantityNormalized) theObj;
217                EqualsBuilder b = new EqualsBuilder();
218                b.append(getHashIdentity(), obj.getHashIdentity());
219                b.append(getHashIdentityAndUnits(), obj.getHashIdentityAndUnits());
220                b.append(getHashIdentitySystemAndUnits(), obj.getHashIdentitySystemAndUnits());
221                b.append(isMissing(), obj.isMissing());
222                b.append(getValue(), obj.getValue());
223                return b.isEquals();
224        }
225
226        @Override
227        public int hashCode() {
228                HashCodeBuilder b = new HashCodeBuilder();
229                b.append(getHashIdentity());
230                b.append(getHashIdentityAndUnits());
231                b.append(getHashIdentitySystemAndUnits());
232                b.append(isMissing());
233                b.append(getValue());
234                return b.toHashCode();
235        }
236
237        @Override
238        public boolean matches(IQueryParameterType theParam) {
239
240                if (!(theParam instanceof QuantityParam)) {
241                        return false;
242                }
243                QuantityParam quantity = (QuantityParam) theParam;
244                boolean retval = false;
245
246                String quantitySystem = quantity.getSystem();
247                BigDecimal quantityValue = quantity.getValue();
248                Double quantityDoubleValue = null;
249                if (quantityValue != null) quantityDoubleValue = quantityValue.doubleValue();
250                String quantityUnits = defaultString(quantity.getUnits());
251
252                // -- convert the value/unit to the canonical form if any, otherwise store the original value/units pair
253                Pair canonicalForm = UcumServiceUtil.getCanonicalForm(quantitySystem, quantityValue, quantityUnits);
254                if (canonicalForm != null) {
255                        quantityDoubleValue = Double.parseDouble(canonicalForm.getValue().asDecimal());
256                        quantityUnits = canonicalForm.getCode();
257                }
258
259                // Only match on system if it wasn't specified
260                if (quantitySystem == null && isBlank(quantityUnits)) {
261                        if (Objects.equals(getValue(), quantityDoubleValue)) {
262                                retval = true;
263                        }
264                } else {
265                        String unitsString = defaultString(getUnits());
266                        if (quantitySystem == null) {
267                                if (unitsString.equalsIgnoreCase(quantityUnits) && Objects.equals(getValue(), quantityDoubleValue)) {
268                                        retval = true;
269                                }
270                        } else if (isBlank(quantityUnits)) {
271                                if (getSystem().equalsIgnoreCase(quantitySystem) && Objects.equals(getValue(), quantityDoubleValue)) {
272                                        retval = true;
273                                }
274                        } else {
275                                if (getSystem().equalsIgnoreCase(quantitySystem)
276                                                && unitsString.equalsIgnoreCase(quantityUnits)
277                                                && Objects.equals(getValue(), quantityDoubleValue)) {
278                                        retval = true;
279                                }
280                        }
281                }
282
283                return retval;
284        }
285
286        @Override
287        public ResourceTable getResource() {
288                return myResource;
289        }
290
291        @Override
292        public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
293                myResource = theResource;
294                setResourceType(theResource.getResourceType());
295                return this;
296        }
297}