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