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